@sanity/sdk-react 3.0.0-rc.1 → 3.0.0-rc.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/dist/index.d.ts +2289 -2702
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4315 -1915
- package/dist/index.js.map +1 -1
- package/package.json +13 -14
- package/src/_exports/sdk-react.ts +9 -6
- package/src/components/SanityApp.tsx +2 -3
- package/src/components/auth/AuthBoundary.test.tsx +1 -19
- package/src/components/auth/AuthBoundary.tsx +2 -1
- package/src/components/auth/LoginError.tsx +2 -3
- package/src/context/ComlinkTokenRefresh.tsx +3 -5
- package/src/context/ResourceProvider.test.tsx +21 -0
- package/src/context/ResourceProvider.tsx +4 -1
- package/src/hooks/agent/agentActions.test.tsx +1 -1
- package/src/hooks/agent/agentActions.ts +1 -1
- package/src/hooks/agent/useAgentResourceContext.ts +1 -1
- package/src/hooks/comlink/useFrameConnection.test.tsx +3 -2
- package/src/hooks/comlink/useFrameConnection.ts +1 -1
- package/src/hooks/comlink/useWindowConnection.test.tsx +4 -3
- package/src/hooks/comlink/useWindowConnection.ts +2 -3
- package/src/hooks/comments/useCommentActions.test.tsx +150 -0
- package/src/hooks/comments/useCommentActions.ts +109 -0
- package/src/hooks/comments/useCommentList.ts +79 -0
- package/src/hooks/comments/useCommentThreads.test.tsx +107 -0
- package/src/hooks/comments/useCommentThreads.ts +73 -0
- package/src/hooks/comments/useComments.test.tsx +242 -0
- package/src/hooks/comments/useComments.ts +59 -0
- package/src/hooks/context/useSanityInstance.test.tsx +2 -54
- package/src/hooks/context/useSanityInstance.ts +2 -24
- package/src/hooks/dashboard/useManageFavorite.ts +1 -1
- package/src/hooks/dashboard/useRecordDocumentHistoryEvent.ts +2 -1
- package/src/hooks/documents/useDocuments.ts +2 -7
- package/src/hooks/helpers/useNormalizedResourceOptions.test.tsx +0 -27
- package/src/hooks/helpers/useNormalizedResourceOptions.ts +14 -25
- package/src/hooks/paginatedDocuments/usePaginatedDocuments.ts +2 -7
- package/src/hooks/presence/usePresence.ts +14 -1
- package/src/hooks/presence/usePresenceForDocument.test.tsx +141 -0
- package/src/hooks/presence/usePresenceForDocument.ts +104 -0
- package/src/hooks/presence/useReportPresence.test.tsx +202 -0
- package/src/hooks/presence/useReportPresence.ts +170 -0
- package/src/hooks/preview/useDocumentPreview.tsx +2 -6
- package/src/hooks/projects/useProjects.ts +0 -8
- package/src/hooks/query/useQuery.ts +3 -8
- package/src/hooks/releases/useActiveReleases.ts +2 -2
- package/src/hooks/releases/useAllReleases.ts +2 -2
- package/src/hooks/users/useUser.ts +2 -8
- package/src/hooks/users/useUsers.ts +1 -2
- package/src/hooks/applications/useCreateUserApplication.test-d.ts +0 -14
- package/src/hooks/applications/useCreateUserApplication.ts +0 -11
- package/src/hooks/applications/useDeleteUserApplication.test-d.ts +0 -14
- package/src/hooks/applications/useDeleteUserApplication.ts +0 -11
- package/src/hooks/applications/useUpdateUserApplication.test-d.ts +0 -14
- package/src/hooks/applications/useUpdateUserApplication.ts +0 -11
- package/src/hooks/applications/useUserApplication.test-d.ts +0 -11
- package/src/hooks/applications/useUserApplication.ts +0 -14
- package/src/hooks/applications/useUserApplications.test-d.ts +0 -11
- package/src/hooks/applications/useUserApplications.ts +0 -14
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sanity/sdk-react",
|
|
3
|
-
"version": "3.0.0-rc.
|
|
3
|
+
"version": "3.0.0-rc.2",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Sanity SDK React toolkit for Content OS",
|
|
6
6
|
"keywords": [
|
|
@@ -33,32 +33,28 @@
|
|
|
33
33
|
"module": "./dist/index.js",
|
|
34
34
|
"types": "./dist/index.d.ts",
|
|
35
35
|
"exports": {
|
|
36
|
-
".":
|
|
37
|
-
"source": "./src/_exports/index.ts",
|
|
38
|
-
"import": "./dist/index.js",
|
|
39
|
-
"default": "./dist/index.js"
|
|
40
|
-
},
|
|
36
|
+
".": "./dist/index.js",
|
|
41
37
|
"./package.json": "./package.json"
|
|
42
38
|
},
|
|
43
39
|
"publishConfig": {
|
|
44
40
|
"access": "public"
|
|
45
41
|
},
|
|
46
42
|
"dependencies": {
|
|
47
|
-
"@sanity/client": "^7.
|
|
48
|
-
"@sanity/message-protocol": "^0.
|
|
49
|
-
"@sanity/types": "^6.
|
|
43
|
+
"@sanity/client": "^7.26.2",
|
|
44
|
+
"@sanity/message-protocol": "^0.24.0",
|
|
45
|
+
"@sanity/types": "^6.9.1",
|
|
50
46
|
"@sanity/workbench": "0.1.0-alpha.24",
|
|
51
47
|
"groq": "3.88.1-typegen-experimental.0",
|
|
52
48
|
"react-compiler-runtime": "^1.0.0",
|
|
53
49
|
"react-error-boundary": "^6.1.2",
|
|
54
50
|
"rxjs": "^7.8.2",
|
|
55
51
|
"xstate": "^5.31.0",
|
|
56
|
-
"@sanity/sdk": "3.0.0-rc.
|
|
52
|
+
"@sanity/sdk": "3.0.0-rc.2"
|
|
57
53
|
},
|
|
58
54
|
"devDependencies": {
|
|
59
55
|
"@sanity/browserslist-config": "^1.0.5",
|
|
60
|
-
"@sanity/comlink": "^4.0.
|
|
61
|
-
"@sanity/pkg-utils": "^
|
|
56
|
+
"@sanity/comlink": "^4.0.3",
|
|
57
|
+
"@sanity/pkg-utils": "^12.1.0",
|
|
62
58
|
"@testing-library/jest-dom": "^6.9.1",
|
|
63
59
|
"@testing-library/react": "^16.3.2",
|
|
64
60
|
"@types/node": "^24.13.3",
|
|
@@ -73,13 +69,13 @@
|
|
|
73
69
|
"oxfmt": "^0.58.0",
|
|
74
70
|
"react": "^19.2.7",
|
|
75
71
|
"react-dom": "^19.2.7",
|
|
76
|
-
"
|
|
72
|
+
"rolldown": "^1.2.3",
|
|
77
73
|
"typescript": "^6.0.3",
|
|
78
74
|
"vite": "^8.1.5",
|
|
79
75
|
"vitest": "^4.1.10",
|
|
76
|
+
"@repo/package.bundle": "3.82.0",
|
|
80
77
|
"@repo/config-eslint": "0.0.0",
|
|
81
78
|
"@repo/config-test": "0.0.1",
|
|
82
|
-
"@repo/package.bundle": "3.82.0",
|
|
83
79
|
"@repo/package.config": "0.0.1",
|
|
84
80
|
"@repo/tsconfig": "0.0.1"
|
|
85
81
|
},
|
|
@@ -88,6 +84,9 @@
|
|
|
88
84
|
"react-dom": "^18.0.0 || ^19.0.0"
|
|
89
85
|
},
|
|
90
86
|
"browserslist": "extends @sanity/browserslist-config",
|
|
87
|
+
"inlinedDependencies": {
|
|
88
|
+
"@sanity/comlink": "4.0.3"
|
|
89
|
+
},
|
|
91
90
|
"scripts": {
|
|
92
91
|
"build": "pkg build --strict --clean --check",
|
|
93
92
|
"build:bundle": "vite build --configLoader runner --config package.bundle.ts",
|
|
@@ -27,13 +27,8 @@ export {
|
|
|
27
27
|
} from '../hooks/agent/useAgentResourceContext'
|
|
28
28
|
export {useApplication} from '../hooks/applications/useApplication'
|
|
29
29
|
export {useApplications} from '../hooks/applications/useApplications'
|
|
30
|
-
export {useCreateUserApplication} from '../hooks/applications/useCreateUserApplication'
|
|
31
30
|
export {useDeleteApplication} from '../hooks/applications/useDeleteApplication'
|
|
32
|
-
export {useDeleteUserApplication} from '../hooks/applications/useDeleteUserApplication'
|
|
33
31
|
export {useUpdateApplication} from '../hooks/applications/useUpdateApplication'
|
|
34
|
-
export {useUpdateUserApplication} from '../hooks/applications/useUpdateUserApplication'
|
|
35
|
-
export {useUserApplication} from '../hooks/applications/useUserApplication'
|
|
36
|
-
export {useUserApplications} from '../hooks/applications/useUserApplications'
|
|
37
32
|
export {useAuthState} from '../hooks/auth/useAuthState'
|
|
38
33
|
export {useAuthToken} from '../hooks/auth/useAuthToken'
|
|
39
34
|
export {useCurrentUser} from '../hooks/auth/useCurrentUser'
|
|
@@ -55,6 +50,9 @@ export {
|
|
|
55
50
|
type WindowConnection,
|
|
56
51
|
type WindowMessageHandler,
|
|
57
52
|
} from '../hooks/comlink/useWindowConnection'
|
|
53
|
+
export {type CommentActions, useCommentActions} from '../hooks/comments/useCommentActions'
|
|
54
|
+
export {useComments, type UseCommentsResult} from '../hooks/comments/useComments'
|
|
55
|
+
export {useCommentThreads, type UseCommentThreadsResult} from '../hooks/comments/useCommentThreads'
|
|
58
56
|
export {useResource} from '../hooks/context/useResource'
|
|
59
57
|
export {useSanityInstance} from '../hooks/context/useSanityInstance'
|
|
60
58
|
export {useDashboardNavigate} from '../hooks/dashboard/useDashboardNavigate'
|
|
@@ -91,6 +89,11 @@ export {
|
|
|
91
89
|
usePaginatedDocuments,
|
|
92
90
|
} from '../hooks/paginatedDocuments/usePaginatedDocuments'
|
|
93
91
|
export {usePresence} from '../hooks/presence/usePresence'
|
|
92
|
+
export {
|
|
93
|
+
usePresenceForDocument,
|
|
94
|
+
type UsePresenceForDocumentOptions,
|
|
95
|
+
} from '../hooks/presence/usePresenceForDocument'
|
|
96
|
+
export {useReportPresence, type UseReportPresenceOptions} from '../hooks/presence/useReportPresence'
|
|
94
97
|
export {
|
|
95
98
|
useDocumentPreview,
|
|
96
99
|
type useDocumentPreviewOptions,
|
|
@@ -102,7 +105,7 @@ export {
|
|
|
102
105
|
type useDocumentProjectionResults,
|
|
103
106
|
} from '../hooks/projection/useDocumentProjection'
|
|
104
107
|
export {useProject} from '../hooks/projects/useProject'
|
|
105
|
-
export {
|
|
108
|
+
export {useProjects} from '../hooks/projects/useProjects'
|
|
106
109
|
export {useQuery} from '../hooks/query/useQuery'
|
|
107
110
|
export {useActiveReleases} from '../hooks/releases/useActiveReleases'
|
|
108
111
|
export {useAllReleases} from '../hooks/releases/useAllReleases'
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {type DocumentResource,
|
|
1
|
+
import {type DocumentResource, type SanityConfig} from '@sanity/sdk'
|
|
2
|
+
import {isStudioConfig} from '@sanity/sdk/_internal'
|
|
2
3
|
import {type ReactElement, useContext, useEffect, useMemo} from 'react'
|
|
3
4
|
|
|
4
5
|
import {SDKStudioContext, type StudioWorkspaceHandle} from '../context/SDKStudioContext'
|
|
@@ -17,8 +18,6 @@ export interface SanityAppProps {
|
|
|
17
18
|
* automatically.
|
|
18
19
|
*/
|
|
19
20
|
config?: SanityConfig | SanityConfig[]
|
|
20
|
-
/** @deprecated use the `config` prop instead. */
|
|
21
|
-
sanityConfigs?: SanityConfig[]
|
|
22
21
|
resources?: Record<string, DocumentResource>
|
|
23
22
|
children: React.ReactNode
|
|
24
23
|
/* Fallback content to show when child components are suspending. Same as the `fallback` prop for React Suspense. */
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {AuthStateType
|
|
1
|
+
import {AuthStateType} from '@sanity/sdk'
|
|
2
2
|
import {render, screen, waitFor} from '@testing-library/react'
|
|
3
3
|
import React from 'react'
|
|
4
4
|
import {type FallbackProps} from 'react-error-boundary'
|
|
@@ -110,24 +110,6 @@ describe('AuthBoundary', () => {
|
|
|
110
110
|
const mockUseVerifyOrgProjects = vi.mocked(useVerifyOrgProjects)
|
|
111
111
|
const testProjectIds = ['proj-test'] // Example project ID for tests
|
|
112
112
|
|
|
113
|
-
// Mock Sanity instance
|
|
114
|
-
const mockSanityInstance = {
|
|
115
|
-
instanceId: 'test-instance-id',
|
|
116
|
-
config: {
|
|
117
|
-
projectId: 'test-project',
|
|
118
|
-
dataset: 'test-dataset',
|
|
119
|
-
},
|
|
120
|
-
isDisposed: () => false,
|
|
121
|
-
dispose: () => {},
|
|
122
|
-
onDispose: () => () => {},
|
|
123
|
-
getParent: () => undefined,
|
|
124
|
-
createChild: (config: SanityConfig) => ({
|
|
125
|
-
...mockSanityInstance,
|
|
126
|
-
config: {...mockSanityInstance.config, ...config},
|
|
127
|
-
}),
|
|
128
|
-
match: () => undefined,
|
|
129
|
-
}
|
|
130
|
-
|
|
131
113
|
beforeEach(() => {
|
|
132
114
|
vi.clearAllMocks()
|
|
133
115
|
consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {CorsOriginError} from '@sanity/client'
|
|
2
|
-
import {AuthStateType, getCorsErrorProjectId, isImportError
|
|
2
|
+
import {AuthStateType, getCorsErrorProjectId, isImportError} from '@sanity/sdk'
|
|
3
|
+
import {isStudioConfig} from '@sanity/sdk/_internal'
|
|
3
4
|
import {useEffect, useMemo} from 'react'
|
|
4
5
|
import {ErrorBoundary, type FallbackProps} from 'react-error-boundary'
|
|
5
6
|
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import {ClientError} from '@sanity/client'
|
|
2
|
+
import {AuthStateType, getIsInDashboardState} from '@sanity/sdk'
|
|
2
3
|
import {
|
|
3
|
-
AuthStateType,
|
|
4
4
|
getClientErrorApiBody,
|
|
5
5
|
getClientErrorApiDescription,
|
|
6
|
-
getIsInDashboardState,
|
|
7
6
|
isProjectUserNotFoundClientError,
|
|
8
|
-
} from '@sanity/sdk'
|
|
7
|
+
} from '@sanity/sdk/_internal'
|
|
9
8
|
import {Suspense, useCallback, useEffect, useMemo, useRef} from 'react'
|
|
10
9
|
import {type FallbackProps} from 'react-error-boundary'
|
|
11
10
|
|
|
@@ -1,15 +1,13 @@
|
|
|
1
1
|
import {type ClientError} from '@sanity/client'
|
|
2
2
|
import {SDK_CHANNEL_NAME, SDK_NODE_NAME} from '@sanity/message-protocol'
|
|
3
|
+
import {AuthStateType, getIsInDashboardState, setAuthToken} from '@sanity/sdk'
|
|
4
|
+
import {isStudioConfig} from '@sanity/sdk/_internal'
|
|
3
5
|
import {
|
|
4
|
-
AuthStateType,
|
|
5
6
|
type FrameMessage,
|
|
6
|
-
getIsInDashboardState,
|
|
7
|
-
isStudioConfig,
|
|
8
7
|
type NewTokenResponseMessage,
|
|
9
8
|
type RequestNewTokenMessage,
|
|
10
|
-
setAuthToken,
|
|
11
9
|
type WindowMessage,
|
|
12
|
-
} from '@sanity/sdk'
|
|
10
|
+
} from '@sanity/sdk/comlink'
|
|
13
11
|
import React, {type PropsWithChildren, useCallback, useEffect, useMemo, useRef} from 'react'
|
|
14
12
|
|
|
15
13
|
import {useAuthState} from '../hooks/auth/useAuthState'
|
|
@@ -59,6 +59,27 @@ describe('ResourceProvider', () => {
|
|
|
59
59
|
consoleSpy.mockRestore()
|
|
60
60
|
})
|
|
61
61
|
|
|
62
|
+
it('renders nothing during loading when fallback is null', async () => {
|
|
63
|
+
const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
|
|
64
|
+
const {promise, resolve} = promiseWithResolvers()
|
|
65
|
+
function SuspendingChild(): React.ReactNode {
|
|
66
|
+
throw promise
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const {container} = render(
|
|
70
|
+
<ResourceProvider {...testConfig} fallback={null}>
|
|
71
|
+
<SuspendingChild />
|
|
72
|
+
</ResourceProvider>,
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
expect(container).toBeEmptyDOMElement()
|
|
76
|
+
act(() => {
|
|
77
|
+
resolve()
|
|
78
|
+
})
|
|
79
|
+
await new Promise((r) => setTimeout(r, 0))
|
|
80
|
+
consoleSpy.mockRestore()
|
|
81
|
+
})
|
|
82
|
+
|
|
62
83
|
it('creates root instance when no parent context exists', async () => {
|
|
63
84
|
const {promise, resolve} = promiseWithResolvers<SanityInstance | null>()
|
|
64
85
|
|
|
@@ -130,7 +130,10 @@ export function ResourceProvider({
|
|
|
130
130
|
}, [instance, parentInstance])
|
|
131
131
|
|
|
132
132
|
return (
|
|
133
|
-
<SanityInstanceProvider
|
|
133
|
+
<SanityInstanceProvider
|
|
134
|
+
instance={instance}
|
|
135
|
+
fallback={fallback === undefined ? DEFAULT_FALLBACK : fallback}
|
|
136
|
+
>
|
|
134
137
|
<ResourceContext.Provider value={effectiveResource}>
|
|
135
138
|
<ProjectContext.Provider value={effectiveProjectId}>
|
|
136
139
|
<PerspectiveContext.Provider value={perspective ?? parentPerspective}>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import {type Events, SDK_CHANNEL_NAME, SDK_NODE_NAME} from '@sanity/message-protocol'
|
|
2
|
-
import {type FrameMessage} from '@sanity/sdk'
|
|
2
|
+
import {type FrameMessage} from '@sanity/sdk/comlink'
|
|
3
3
|
import {useCallback, useEffect, useRef} from 'react'
|
|
4
4
|
|
|
5
5
|
import {useWindowConnection} from '../comlink/useWindowConnection'
|
|
@@ -4,7 +4,7 @@ import {beforeEach, describe, expect, it, vi} from 'vitest'
|
|
|
4
4
|
import {act, renderHook} from '../../../test/test-utils'
|
|
5
5
|
import {useFrameConnection} from './useFrameConnection'
|
|
6
6
|
|
|
7
|
-
vi.mock(import('@sanity/sdk'), async (importOriginal) => {
|
|
7
|
+
vi.mock(import('@sanity/sdk/comlink'), async (importOriginal) => {
|
|
8
8
|
const actual = await importOriginal()
|
|
9
9
|
return {
|
|
10
10
|
...actual,
|
|
@@ -14,7 +14,8 @@ vi.mock(import('@sanity/sdk'), async (importOriginal) => {
|
|
|
14
14
|
}
|
|
15
15
|
})
|
|
16
16
|
|
|
17
|
-
const {getOrCreateChannel, getOrCreateController, releaseChannel} =
|
|
17
|
+
const {getOrCreateChannel, getOrCreateController, releaseChannel} =
|
|
18
|
+
await import('@sanity/sdk/comlink')
|
|
18
19
|
|
|
19
20
|
interface TestControllerMessage {
|
|
20
21
|
type: 'TEST_MESSAGE'
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {type Message, type Node} from '@sanity/comlink'
|
|
2
|
-
import {
|
|
2
|
+
import {type StateSource} from '@sanity/sdk'
|
|
3
|
+
import {getNodeState, type NodeState} from '@sanity/sdk/comlink'
|
|
3
4
|
import {screen} from '@testing-library/react'
|
|
4
5
|
import {Suspense} from 'react'
|
|
5
6
|
import {beforeEach, describe, expect, it, vi} from 'vitest'
|
|
@@ -7,8 +8,8 @@ import {beforeEach, describe, expect, it, vi} from 'vitest'
|
|
|
7
8
|
import {render, renderHook} from '../../../test/test-utils'
|
|
8
9
|
import {useWindowConnection} from './useWindowConnection'
|
|
9
10
|
|
|
10
|
-
vi.mock('@sanity/sdk', async () => {
|
|
11
|
-
const actual = await vi.importActual('@sanity/sdk')
|
|
11
|
+
vi.mock('@sanity/sdk/comlink', async () => {
|
|
12
|
+
const actual = await vi.importActual('@sanity/sdk/comlink')
|
|
12
13
|
return {
|
|
13
14
|
...actual,
|
|
14
15
|
getNodeState: vi.fn(),
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import {type MessageData, type NodeInput} from '@sanity/comlink'
|
|
2
|
+
import {type SanityInstance, type StateSource} from '@sanity/sdk'
|
|
2
3
|
import {
|
|
3
4
|
type FrameMessage,
|
|
4
5
|
getNodeState,
|
|
5
6
|
type NodeState,
|
|
6
|
-
type SanityInstance,
|
|
7
|
-
type StateSource,
|
|
8
7
|
type WindowMessage,
|
|
9
|
-
} from '@sanity/sdk'
|
|
8
|
+
} from '@sanity/sdk/comlink'
|
|
10
9
|
import {useCallback, useEffect, useRef} from 'react'
|
|
11
10
|
import {filter, firstValueFrom} from 'rxjs'
|
|
12
11
|
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createComment,
|
|
3
|
+
removeComment,
|
|
4
|
+
replyToComment,
|
|
5
|
+
setCommentStatus,
|
|
6
|
+
updateComment,
|
|
7
|
+
} from '@sanity/sdk'
|
|
8
|
+
import {renderHook} from '@testing-library/react'
|
|
9
|
+
import {beforeEach, describe, expect, it, vi} from 'vitest'
|
|
10
|
+
|
|
11
|
+
import {ResourceProvider} from '../../context/ResourceProvider'
|
|
12
|
+
import {ResourcesContext} from '../../context/ResourcesContext'
|
|
13
|
+
import {useCommentActions} from './useCommentActions'
|
|
14
|
+
|
|
15
|
+
vi.mock('@sanity/sdk', async (importOriginal) => {
|
|
16
|
+
const original = await importOriginal<typeof import('@sanity/sdk')>()
|
|
17
|
+
return {
|
|
18
|
+
...original,
|
|
19
|
+
createComment: vi.fn(),
|
|
20
|
+
replyToComment: vi.fn(),
|
|
21
|
+
updateComment: vi.fn(),
|
|
22
|
+
setCommentStatus: vi.fn(),
|
|
23
|
+
removeComment: vi.fn(),
|
|
24
|
+
}
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
const HANDLE = {documentId: 'doc-1', documentType: 'author'}
|
|
28
|
+
|
|
29
|
+
/** Creates name a field, since a comment with no path is refused. */
|
|
30
|
+
const CREATE = {...HANDLE, fieldPath: 'name'}
|
|
31
|
+
|
|
32
|
+
// Hoisted: an inline object here would be a new value on every render, and the
|
|
33
|
+
// callbacks are memoised against it.
|
|
34
|
+
const RESOURCES = {other: {projectId: 'p2', dataset: 'd2'}}
|
|
35
|
+
|
|
36
|
+
/** Hoisted for the same reason as `RESOURCES`. */
|
|
37
|
+
const RELEASE_PERSPECTIVE = {releaseName: 'summer'}
|
|
38
|
+
|
|
39
|
+
function Wrapper({children}: {children: React.ReactNode}) {
|
|
40
|
+
return (
|
|
41
|
+
<ResourceProvider projectId="p" dataset="d" fallback={null}>
|
|
42
|
+
<ResourcesContext.Provider value={RESOURCES}>{children}</ResourcesContext.Provider>
|
|
43
|
+
</ResourceProvider>
|
|
44
|
+
)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function PerspectiveWrapper({children}: {children: React.ReactNode}) {
|
|
48
|
+
return (
|
|
49
|
+
<ResourceProvider projectId="p" dataset="d" perspective={RELEASE_PERSPECTIVE} fallback={null}>
|
|
50
|
+
<ResourcesContext.Provider value={RESOURCES}>{children}</ResourcesContext.Provider>
|
|
51
|
+
</ResourceProvider>
|
|
52
|
+
)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function setup(wrapper: typeof Wrapper = Wrapper) {
|
|
56
|
+
return renderHook(() => useCommentActions(), {wrapper})
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
describe('useCommentActions', () => {
|
|
60
|
+
beforeEach(() => {
|
|
61
|
+
vi.resetAllMocks()
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
it('keeps the callbacks stable across renders', () => {
|
|
65
|
+
const {result, rerender} = setup()
|
|
66
|
+
const first = result.current
|
|
67
|
+
|
|
68
|
+
rerender()
|
|
69
|
+
|
|
70
|
+
expect(result.current).toBe(first)
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
it('forwards each action to the store', () => {
|
|
74
|
+
const {result} = setup()
|
|
75
|
+
|
|
76
|
+
result.current.createComment({...CREATE, message: null})
|
|
77
|
+
result.current.replyToComment({...HANDLE, parentCommentId: 'p1', message: null})
|
|
78
|
+
result.current.updateComment({commentId: 'c1', message: null})
|
|
79
|
+
result.current.setCommentStatus({commentId: 'c1', status: 'resolved'})
|
|
80
|
+
result.current.removeComment({commentId: 'c1'})
|
|
81
|
+
|
|
82
|
+
expect(createComment).toHaveBeenCalledOnce()
|
|
83
|
+
expect(replyToComment).toHaveBeenCalledOnce()
|
|
84
|
+
expect(updateComment).toHaveBeenCalledOnce()
|
|
85
|
+
expect(setCommentStatus).toHaveBeenCalledOnce()
|
|
86
|
+
expect(removeComment).toHaveBeenCalledOnce()
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
it('fills in the resource from context', () => {
|
|
90
|
+
const {result} = setup()
|
|
91
|
+
|
|
92
|
+
result.current.createComment({...CREATE, message: null})
|
|
93
|
+
|
|
94
|
+
expect(createComment).toHaveBeenCalledWith(
|
|
95
|
+
expect.anything(),
|
|
96
|
+
expect.objectContaining({resource: {projectId: 'p', dataset: 'd'}}),
|
|
97
|
+
)
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
it('lets a call choose a different named resource', () => {
|
|
101
|
+
// Resolution happens per call, so one set of callbacks serves several
|
|
102
|
+
// resources.
|
|
103
|
+
const {result} = setup()
|
|
104
|
+
|
|
105
|
+
result.current.removeComment({commentId: 'c1', resourceName: 'other'})
|
|
106
|
+
|
|
107
|
+
expect(removeComment).toHaveBeenCalledWith(
|
|
108
|
+
expect.anything(),
|
|
109
|
+
expect.objectContaining({resource: {projectId: 'p2', dataset: 'd2'}}),
|
|
110
|
+
)
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
it('rejects a call naming a resource that is not in context', () => {
|
|
114
|
+
const {result} = setup()
|
|
115
|
+
|
|
116
|
+
expect(() => result.current.removeComment({commentId: 'c1', resourceName: 'missing'})).toThrow(
|
|
117
|
+
/no resource named "missing"/,
|
|
118
|
+
)
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
it('fills in the perspective from context', () => {
|
|
122
|
+
// Core turns a release perspective into `target.documentVersionId`, so
|
|
123
|
+
// losing it here files the comment against the wrong release and nothing
|
|
124
|
+
// reports an error.
|
|
125
|
+
const {result} = setup(PerspectiveWrapper)
|
|
126
|
+
|
|
127
|
+
result.current.createComment({...CREATE, message: null})
|
|
128
|
+
result.current.replyToComment({...HANDLE, parentCommentId: 'p1', message: null})
|
|
129
|
+
|
|
130
|
+
expect(createComment).toHaveBeenCalledWith(
|
|
131
|
+
expect.anything(),
|
|
132
|
+
expect.objectContaining({perspective: RELEASE_PERSPECTIVE}),
|
|
133
|
+
)
|
|
134
|
+
expect(replyToComment).toHaveBeenCalledWith(
|
|
135
|
+
expect.anything(),
|
|
136
|
+
expect.objectContaining({perspective: RELEASE_PERSPECTIVE}),
|
|
137
|
+
)
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
it('lets a call override the perspective from context', () => {
|
|
141
|
+
const {result} = setup(PerspectiveWrapper)
|
|
142
|
+
|
|
143
|
+
result.current.createComment({...CREATE, message: null, perspective: 'published'})
|
|
144
|
+
|
|
145
|
+
expect(createComment).toHaveBeenCalledWith(
|
|
146
|
+
expect.anything(),
|
|
147
|
+
expect.objectContaining({perspective: 'published'}),
|
|
148
|
+
)
|
|
149
|
+
})
|
|
150
|
+
})
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type Comment,
|
|
3
|
+
createComment,
|
|
4
|
+
type CreateCommentOptions,
|
|
5
|
+
type DocumentResource,
|
|
6
|
+
type PerspectiveHandle,
|
|
7
|
+
removeComment,
|
|
8
|
+
type RemoveCommentOptions,
|
|
9
|
+
replyToComment,
|
|
10
|
+
type ReplyToCommentOptions,
|
|
11
|
+
setCommentStatus,
|
|
12
|
+
type SetCommentStatusOptions,
|
|
13
|
+
updateComment,
|
|
14
|
+
type UpdateCommentOptions,
|
|
15
|
+
} from '@sanity/sdk'
|
|
16
|
+
import {useContext, useMemo} from 'react'
|
|
17
|
+
|
|
18
|
+
import {PerspectiveContext} from '../../context/PerspectiveContext'
|
|
19
|
+
import {ResourcesContext} from '../../context/ResourcesContext'
|
|
20
|
+
import {useSanityInstance} from '../context/useSanityInstance'
|
|
21
|
+
import {
|
|
22
|
+
normalizeResourceOptions,
|
|
23
|
+
useEffectiveContextResource,
|
|
24
|
+
type WithResourceNameSupport,
|
|
25
|
+
} from '../helpers/useNormalizedResourceOptions'
|
|
26
|
+
import {trackHookUsage} from '../helpers/useTrackHookUsage'
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* @public
|
|
30
|
+
* @category Types
|
|
31
|
+
*/
|
|
32
|
+
export interface CommentActions {
|
|
33
|
+
/** Starts a thread on a document, or on one of its fields. */
|
|
34
|
+
createComment: (options: WithResourceNameSupport<CreateCommentOptions>) => Promise<Comment>
|
|
35
|
+
/** Adds a reply to an existing thread. */
|
|
36
|
+
replyToComment: (options: WithResourceNameSupport<ReplyToCommentOptions>) => Promise<Comment>
|
|
37
|
+
/** Rewrites a comment's message. */
|
|
38
|
+
updateComment: (options: WithResourceNameSupport<UpdateCommentOptions>) => Promise<void>
|
|
39
|
+
/** Resolves or reopens a thread. Pass the thread's first comment. */
|
|
40
|
+
setCommentStatus: (options: WithResourceNameSupport<SetCommentStatusOptions>) => Promise<void>
|
|
41
|
+
/** Deletes a comment, and its replies when it starts a thread. */
|
|
42
|
+
removeComment: (options: WithResourceNameSupport<RemoveCommentOptions>) => Promise<void>
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
type Resolvable = {
|
|
46
|
+
resource?: DocumentResource
|
|
47
|
+
resourceName?: string
|
|
48
|
+
projectId?: string
|
|
49
|
+
dataset?: string
|
|
50
|
+
perspective?: PerspectiveHandle['perspective']
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Returns the five comment write actions, bound to the current instance.
|
|
55
|
+
*
|
|
56
|
+
* Each writes optimistically: the change shows immediately and is rolled back
|
|
57
|
+
* if the server rejects it, so an app can render straight from
|
|
58
|
+
* {@link useComments} without tracking pending state itself.
|
|
59
|
+
*
|
|
60
|
+
* Creating is the exception, and `replyToComment` counts as creating. A comment
|
|
61
|
+
* that fails to post stays on screen carrying `_state.createError` rather than
|
|
62
|
+
* disappearing, so passing the same `commentId` again retries it instead of
|
|
63
|
+
* losing what someone typed.
|
|
64
|
+
*
|
|
65
|
+
* The resource is resolved when an action is called rather than when the hook
|
|
66
|
+
* runs, so one set of callbacks works across several resources. Pass `resource`
|
|
67
|
+
* or `resourceName` per call to choose; otherwise the surrounding
|
|
68
|
+
* `ResourceProvider` decides.
|
|
69
|
+
*
|
|
70
|
+
* @category Comments
|
|
71
|
+
* @function
|
|
72
|
+
* @returns The five write actions
|
|
73
|
+
*
|
|
74
|
+
* @example Resolve a thread
|
|
75
|
+
* ```tsx
|
|
76
|
+
* function ResolveButton({commentId}: {commentId: string}) {
|
|
77
|
+
* const {setCommentStatus} = useCommentActions()
|
|
78
|
+
*
|
|
79
|
+
* return (
|
|
80
|
+
* <button onClick={() => setCommentStatus({commentId, status: 'resolved'})}>
|
|
81
|
+
* Resolve
|
|
82
|
+
* </button>
|
|
83
|
+
* )
|
|
84
|
+
* }
|
|
85
|
+
* ```
|
|
86
|
+
*
|
|
87
|
+
* @public
|
|
88
|
+
*/
|
|
89
|
+
export function useCommentActions(): CommentActions {
|
|
90
|
+
const instance = useSanityInstance()
|
|
91
|
+
trackHookUsage(instance, 'useCommentActions')
|
|
92
|
+
|
|
93
|
+
const resources = useContext(ResourcesContext)
|
|
94
|
+
const contextResource = useEffectiveContextResource()
|
|
95
|
+
const contextPerspective = useContext(PerspectiveContext)
|
|
96
|
+
|
|
97
|
+
return useMemo(() => {
|
|
98
|
+
const resolve = <T extends Resolvable>(options: T) =>
|
|
99
|
+
normalizeResourceOptions(options, resources, contextResource, contextPerspective)
|
|
100
|
+
|
|
101
|
+
return {
|
|
102
|
+
createComment: (options) => createComment(instance, resolve(options)),
|
|
103
|
+
replyToComment: (options) => replyToComment(instance, resolve(options)),
|
|
104
|
+
updateComment: (options) => updateComment(instance, resolve(options)),
|
|
105
|
+
setCommentStatus: (options) => setCommentStatus(instance, resolve(options)),
|
|
106
|
+
removeComment: (options) => removeComment(instance, resolve(options)),
|
|
107
|
+
}
|
|
108
|
+
}, [contextPerspective, contextResource, instance, resources])
|
|
109
|
+
}
|