sanity-plugin-media 4.3.5 → 5.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/dist/index.d.ts +190 -413
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2532 -3564
- package/dist/index.js.map +1 -1
- package/package.json +29 -35
- package/dist/index.cjs +0 -5753
- package/dist/index.cjs.map +0 -1
- package/dist/index.d.cts +0 -462
- package/sanity.json +0 -8
- package/src/__tests__/fixtures/createEpicTestStore.ts +0 -28
- package/src/__tests__/fixtures/listenMock.ts +0 -9
- package/src/__tests__/fixtures/mockSanityClient.ts +0 -84
- package/src/__tests__/fixtures/renderWithProviders.tsx +0 -55
- package/src/__tests__/fixtures/rootState.ts +0 -27
- package/src/__tests__/fixtures/withinDialog.ts +0 -28
- package/src/components/AssetGridVirtualized/index.tsx +0 -94
- package/src/components/AssetMetadata/index.tsx +0 -122
- package/src/components/AssetTableVirtualized/index.tsx +0 -73
- package/src/components/AutoTagInputWrapper/index.tsx +0 -87
- package/src/components/Browser/Browser.test.tsx +0 -45
- package/src/components/Browser/index.tsx +0 -90
- package/src/components/Browser/useBrowserInit.ts +0 -126
- package/src/components/ButtonAssetCopy/index.tsx +0 -65
- package/src/components/ButtonViewGroup/index.tsx +0 -39
- package/src/components/CardAsset/CardAsset.test.tsx +0 -323
- package/src/components/CardAsset/index.tsx +0 -290
- package/src/components/CardUpload/index.tsx +0 -161
- package/src/components/Controls/index.tsx +0 -136
- package/src/components/DebugControls/index.tsx +0 -80
- package/src/components/Dialog/index.tsx +0 -11
- package/src/components/DialogAssetEdit/Details.tsx +0 -181
- package/src/components/DialogAssetEdit/DialogAssetEdit.test.tsx +0 -216
- package/src/components/DialogAssetEdit/index.tsx +0 -492
- package/src/components/DialogConfirm/index.tsx +0 -88
- package/src/components/DialogSearchFacets/index.tsx +0 -42
- package/src/components/DialogTagCreate/DialogTagCreate.test.tsx +0 -121
- package/src/components/DialogTagCreate/index.tsx +0 -103
- package/src/components/DialogTagEdit/DialogTagEdit.test.tsx +0 -165
- package/src/components/DialogTagEdit/index.tsx +0 -190
- package/src/components/DialogTags/index.tsx +0 -45
- package/src/components/Dialogs/index.tsx +0 -76
- package/src/components/DocumentList/index.tsx +0 -62
- package/src/components/FileAssetPreview/index.tsx +0 -37
- package/src/components/FileIcon/index.tsx +0 -43
- package/src/components/FormBuilderTool/FormBuilderTool.test.tsx +0 -63
- package/src/components/FormBuilderTool/index.tsx +0 -69
- package/src/components/FormFieldInputLabel/index.tsx +0 -66
- package/src/components/FormFieldInputTags/index.tsx +0 -98
- package/src/components/FormFieldInputText/index.tsx +0 -41
- package/src/components/FormFieldInputTextarea/index.tsx +0 -43
- package/src/components/FormSubmitButton/index.tsx +0 -59
- package/src/components/Header/index.tsx +0 -80
- package/src/components/Image/index.tsx +0 -41
- package/src/components/Items/index.tsx +0 -68
- package/src/components/Notifications/index.tsx +0 -24
- package/src/components/OrderSelect/index.tsx +0 -66
- package/src/components/PickedBar/index.tsx +0 -77
- package/src/components/Progress/index.tsx +0 -38
- package/src/components/ReduxProvider/index.tsx +0 -96
- package/src/components/SearchFacet/index.tsx +0 -66
- package/src/components/SearchFacetNumber/index.tsx +0 -133
- package/src/components/SearchFacetSelect/index.tsx +0 -110
- package/src/components/SearchFacetString/index.tsx +0 -88
- package/src/components/SearchFacetTags/index.tsx +0 -121
- package/src/components/SearchFacets/index.tsx +0 -72
- package/src/components/SearchFacetsControl/index.tsx +0 -140
- package/src/components/TableHeader/index.tsx +0 -110
- package/src/components/TableHeaderItem/index.tsx +0 -61
- package/src/components/TableRowAsset/index.tsx +0 -420
- package/src/components/TableRowUpload/index.tsx +0 -164
- package/src/components/Tag/index.tsx +0 -200
- package/src/components/TagIcon/index.tsx +0 -22
- package/src/components/TagView/index.tsx +0 -39
- package/src/components/TagViewHeader/index.tsx +0 -70
- package/src/components/TagsPanel/index.tsx +0 -40
- package/src/components/TagsVirtualized/index.tsx +0 -160
- package/src/components/TextInputNumber/index.tsx +0 -32
- package/src/components/TextInputSearch/index.tsx +0 -60
- package/src/components/Tool/index.tsx +0 -13
- package/src/components/UploadDropzone/UploadDropzone.test.tsx +0 -40
- package/src/components/UploadDropzone/index.tsx +0 -173
- package/src/config/orders.ts +0 -28
- package/src/config/searchFacets.ts +0 -312
- package/src/constants.ts +0 -87
- package/src/contexts/AssetSourceDispatchContext.tsx +0 -37
- package/src/contexts/DropzoneDispatchContext.tsx +0 -34
- package/src/contexts/ToolOptionsContext.tsx +0 -65
- package/src/formSchema/index.test.ts +0 -56
- package/src/formSchema/index.ts +0 -39
- package/src/hooks/useBreakpointIndex.ts +0 -49
- package/src/hooks/useKeyPress.ts +0 -39
- package/src/hooks/useOnScreen.ts +0 -34
- package/src/hooks/usePortalPopoverProps.ts +0 -13
- package/src/hooks/useTypedSelector.ts +0 -7
- package/src/hooks/useVersionedClient.ts +0 -6
- package/src/index.ts +0 -5
- package/src/modules/assets/actions.ts +0 -42
- package/src/modules/assets/deleteAndUpdateEpics.test.ts +0 -87
- package/src/modules/assets/fetchEpic.test.ts +0 -73
- package/src/modules/assets/index.ts +0 -825
- package/src/modules/assets/reducer.test.ts +0 -91
- package/src/modules/assets/tagsAndListenerEpics.test.ts +0 -206
- package/src/modules/debug/index.ts +0 -28
- package/src/modules/dialog/actions.ts +0 -10
- package/src/modules/dialog/epics.test.ts +0 -168
- package/src/modules/dialog/index.ts +0 -238
- package/src/modules/dialog/reducer.test.ts +0 -185
- package/src/modules/index.ts +0 -124
- package/src/modules/notifications/epics.test.ts +0 -374
- package/src/modules/notifications/index.ts +0 -199
- package/src/modules/notifications/reducer.test.ts +0 -54
- package/src/modules/search/index.test.ts +0 -36
- package/src/modules/search/index.ts +0 -167
- package/src/modules/selected/index.ts +0 -22
- package/src/modules/selectors.test.ts +0 -21
- package/src/modules/selectors.ts +0 -17
- package/src/modules/tags/epics.test.ts +0 -96
- package/src/modules/tags/index.test.ts +0 -42
- package/src/modules/tags/index.ts +0 -540
- package/src/modules/types.ts +0 -3
- package/src/modules/uploads/actions.ts +0 -13
- package/src/modules/uploads/epics.test.ts +0 -109
- package/src/modules/uploads/index.test.ts +0 -59
- package/src/modules/uploads/index.ts +0 -282
- package/src/operators/checkTagName.test.ts +0 -29
- package/src/operators/checkTagName.ts +0 -33
- package/src/operators/debugThrottle.ts +0 -25
- package/src/plugin.tsx +0 -54
- package/src/schemas/tag.ts +0 -28
- package/src/styled/GlobalStyles/index.tsx +0 -40
- package/src/styled/react-select/creatable.tsx +0 -184
- package/src/styled/react-select/single.tsx +0 -184
- package/src/types/index.ts +0 -379
- package/src/types/sanity-ui.d.ts +0 -6
- package/src/utils/applyMediaTags.ts +0 -87
- package/src/utils/blocksToText.test.ts +0 -43
- package/src/utils/blocksToText.ts +0 -27
- package/src/utils/constructFilter.test.ts +0 -120
- package/src/utils/constructFilter.ts +0 -98
- package/src/utils/generatePreviewBlobUrl.test.ts +0 -70
- package/src/utils/generatePreviewBlobUrl.ts +0 -53
- package/src/utils/getAssetResolution.test.ts +0 -13
- package/src/utils/getAssetResolution.ts +0 -7
- package/src/utils/getDocumentAssetIds.test.ts +0 -50
- package/src/utils/getDocumentAssetIds.ts +0 -35
- package/src/utils/getSchemeColor.test.ts +0 -12
- package/src/utils/getSchemeColor.ts +0 -43
- package/src/utils/getTagSelectOptions.test.ts +0 -44
- package/src/utils/getTagSelectOptions.ts +0 -16
- package/src/utils/getUniqueDocuments.test.ts +0 -26
- package/src/utils/getUniqueDocuments.ts +0 -15
- package/src/utils/imageDprUrl.test.ts +0 -46
- package/src/utils/imageDprUrl.ts +0 -27
- package/src/utils/isSupportedAssetType.test.ts +0 -16
- package/src/utils/isSupportedAssetType.ts +0 -15
- package/src/utils/mediaField.ts +0 -73
- package/src/utils/sanitizeFormData.test.ts +0 -59
- package/src/utils/sanitizeFormData.ts +0 -26
- package/src/utils/typeGuards.test.ts +0 -18
- package/src/utils/typeGuards.ts +0 -9
- package/src/utils/uploadSanityAsset.test.ts +0 -29
- package/src/utils/uploadSanityAsset.ts +0 -97
- package/src/utils/withMaxConcurrency.test.ts +0 -43
- package/src/utils/withMaxConcurrency.ts +0 -55
- package/src/utils/zodFormResolver.ts +0 -17
- package/v2-incompatible.js +0 -11
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
import {screen, waitFor} from '@testing-library/react'
|
|
2
|
-
import userEvent from '@testing-library/user-event'
|
|
3
|
-
import {describe, expect, it, vi} from 'vitest'
|
|
4
|
-
|
|
5
|
-
import {renderWithProviders} from '../../__tests__/fixtures/renderWithProviders'
|
|
6
|
-
import {createTestRootState} from '../../__tests__/fixtures/rootState'
|
|
7
|
-
import {getDialogRoot, inputByName, withinDialog} from '../../__tests__/fixtures/withinDialog'
|
|
8
|
-
import {tagsActions} from '../../modules/tags'
|
|
9
|
-
import DialogTagCreate from './index'
|
|
10
|
-
|
|
11
|
-
describe('DialogTagCreate', () => {
|
|
12
|
-
it('dispatches tag create flow when form is valid', async () => {
|
|
13
|
-
const user = userEvent.setup()
|
|
14
|
-
const {store} = renderWithProviders(
|
|
15
|
-
<DialogTagCreate dialog={{id: 'dlg-1', type: 'tagCreate'}}>
|
|
16
|
-
<span />
|
|
17
|
-
</DialogTagCreate>,
|
|
18
|
-
)
|
|
19
|
-
|
|
20
|
-
const dlg = withinDialog(/create tag/i, screen)
|
|
21
|
-
await user.type(inputByName(/create tag/i, screen, 'name'), 'my-tag')
|
|
22
|
-
await user.click(dlg.getByRole('button', {name: /save and close/i}))
|
|
23
|
-
|
|
24
|
-
expect(store.getState().tags.creating).toBe(true)
|
|
25
|
-
})
|
|
26
|
-
|
|
27
|
-
it('dispatches createRequest with a trimmed tag name', async () => {
|
|
28
|
-
const user = userEvent.setup()
|
|
29
|
-
const {store} = renderWithProviders(
|
|
30
|
-
<DialogTagCreate dialog={{id: 'dlg-1', type: 'tagCreate'}}>
|
|
31
|
-
<span />
|
|
32
|
-
</DialogTagCreate>,
|
|
33
|
-
)
|
|
34
|
-
const dispatchSpy = vi.spyOn(store, 'dispatch')
|
|
35
|
-
const dlg = withinDialog(/create tag/i, screen)
|
|
36
|
-
|
|
37
|
-
await user.type(inputByName(/create tag/i, screen, 'name'), ' spaced ')
|
|
38
|
-
await user.click(dlg.getByRole('button', {name: /save and close/i}))
|
|
39
|
-
|
|
40
|
-
await waitFor(() => {
|
|
41
|
-
let createAction
|
|
42
|
-
for (const call of dispatchSpy.mock.calls) {
|
|
43
|
-
const action = call[0]
|
|
44
|
-
if (tagsActions.createRequest.match(action)) {
|
|
45
|
-
createAction = action
|
|
46
|
-
break
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
expect(createAction).toBeDefined()
|
|
50
|
-
expect(createAction?.payload).toEqual({name: 'spaced'})
|
|
51
|
-
})
|
|
52
|
-
})
|
|
53
|
-
|
|
54
|
-
it('keeps Save disabled until the name is non-empty and valid', async () => {
|
|
55
|
-
const user = userEvent.setup()
|
|
56
|
-
renderWithProviders(
|
|
57
|
-
<DialogTagCreate dialog={{id: 'dlg-1', type: 'tagCreate'}}>
|
|
58
|
-
<span />
|
|
59
|
-
</DialogTagCreate>,
|
|
60
|
-
)
|
|
61
|
-
|
|
62
|
-
expect(
|
|
63
|
-
withinDialog(/create tag/i, screen).getByRole('button', {name: /save and close/i}),
|
|
64
|
-
).toBeDisabled()
|
|
65
|
-
|
|
66
|
-
const nameInput = inputByName(/create tag/i, screen, 'name')
|
|
67
|
-
await user.type(nameInput, 'a')
|
|
68
|
-
await user.tab()
|
|
69
|
-
await waitFor(() => {
|
|
70
|
-
expect(
|
|
71
|
-
withinDialog(/create tag/i, screen).getByRole('button', {name: /save and close/i}),
|
|
72
|
-
).not.toBeDisabled()
|
|
73
|
-
})
|
|
74
|
-
})
|
|
75
|
-
|
|
76
|
-
it('clears the entire dialog stack when the dialog close control is used', async () => {
|
|
77
|
-
const user = userEvent.setup()
|
|
78
|
-
const base = createTestRootState({
|
|
79
|
-
dialog: {
|
|
80
|
-
items: [
|
|
81
|
-
{id: 'dlg-1', type: 'tagCreate'},
|
|
82
|
-
{id: 'tags', type: 'tags'},
|
|
83
|
-
],
|
|
84
|
-
},
|
|
85
|
-
})
|
|
86
|
-
|
|
87
|
-
const {store} = renderWithProviders(
|
|
88
|
-
<DialogTagCreate dialog={{id: 'dlg-1', type: 'tagCreate'}}>
|
|
89
|
-
<span />
|
|
90
|
-
</DialogTagCreate>,
|
|
91
|
-
{preloaded: base},
|
|
92
|
-
)
|
|
93
|
-
|
|
94
|
-
const dlg = withinDialog(/create tag/i, screen)
|
|
95
|
-
await user.click(dlg.getByRole('button', {name: /close dialog/i}))
|
|
96
|
-
|
|
97
|
-
expect(store.getState().dialog.items).toEqual([])
|
|
98
|
-
})
|
|
99
|
-
|
|
100
|
-
it('shows an error indicator beside the name when tag creation failed on the server', async () => {
|
|
101
|
-
const base = createTestRootState({
|
|
102
|
-
tags: {
|
|
103
|
-
...createTestRootState().tags,
|
|
104
|
-
creatingError: {message: 'Tag already exists', statusCode: 409},
|
|
105
|
-
},
|
|
106
|
-
})
|
|
107
|
-
|
|
108
|
-
renderWithProviders(
|
|
109
|
-
<DialogTagCreate dialog={{id: 'dlg-1', type: 'tagCreate'}}>
|
|
110
|
-
<span />
|
|
111
|
-
</DialogTagCreate>,
|
|
112
|
-
{preloaded: base},
|
|
113
|
-
)
|
|
114
|
-
|
|
115
|
-
await waitFor(() => {
|
|
116
|
-
expect(
|
|
117
|
-
getDialogRoot(/create tag/i, screen).querySelector('[data-sanity-icon="error-outline"]'),
|
|
118
|
-
).toBeTruthy()
|
|
119
|
-
})
|
|
120
|
-
})
|
|
121
|
-
})
|
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
import {Box, Flex} from '@sanity/ui'
|
|
2
|
-
import {type ReactNode, useEffect} from 'react'
|
|
3
|
-
import {type SubmitHandler, useForm} from 'react-hook-form'
|
|
4
|
-
import {useDispatch} from 'react-redux'
|
|
5
|
-
|
|
6
|
-
import {tagFormSchema} from '../../formSchema'
|
|
7
|
-
import useTypedSelector from '../../hooks/useTypedSelector'
|
|
8
|
-
import {dialogActions} from '../../modules/dialog'
|
|
9
|
-
import {tagsActions} from '../../modules/tags'
|
|
10
|
-
import type {DialogTagCreateProps, TagFormData} from '../../types'
|
|
11
|
-
import sanitizeFormData from '../../utils/sanitizeFormData'
|
|
12
|
-
import zodFormResolver from '../../utils/zodFormResolver'
|
|
13
|
-
import Dialog from '../Dialog'
|
|
14
|
-
import FormFieldInputText from '../FormFieldInputText'
|
|
15
|
-
import FormSubmitButton from '../FormSubmitButton'
|
|
16
|
-
|
|
17
|
-
type Props = {
|
|
18
|
-
children: ReactNode
|
|
19
|
-
dialog: DialogTagCreateProps
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
const DialogTagCreate = (props: Props) => {
|
|
23
|
-
const {
|
|
24
|
-
children,
|
|
25
|
-
dialog: {id},
|
|
26
|
-
} = props
|
|
27
|
-
|
|
28
|
-
const dispatch = useDispatch()
|
|
29
|
-
|
|
30
|
-
const creating = useTypedSelector((state) => state.tags.creating)
|
|
31
|
-
const creatingError = useTypedSelector((state) => state.tags.creatingError)
|
|
32
|
-
|
|
33
|
-
const {
|
|
34
|
-
// Read the formState before render to subscribe the form state through Proxy
|
|
35
|
-
formState: {errors, isDirty, isValid},
|
|
36
|
-
handleSubmit,
|
|
37
|
-
register,
|
|
38
|
-
setError,
|
|
39
|
-
} = useForm<TagFormData>({
|
|
40
|
-
defaultValues: {
|
|
41
|
-
name: '',
|
|
42
|
-
},
|
|
43
|
-
mode: 'onChange',
|
|
44
|
-
resolver: zodFormResolver<TagFormData>(tagFormSchema),
|
|
45
|
-
})
|
|
46
|
-
|
|
47
|
-
const formUpdating = creating
|
|
48
|
-
|
|
49
|
-
const handleClose = () => {
|
|
50
|
-
dispatch(dialogActions.clear())
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// - submit react-hook-form
|
|
54
|
-
const onSubmit: SubmitHandler<TagFormData> = (formData) => {
|
|
55
|
-
const sanitizedFormData = sanitizeFormData(formData)
|
|
56
|
-
|
|
57
|
-
dispatch(tagsActions.createRequest({name: sanitizedFormData.name}))
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
useEffect(() => {
|
|
61
|
-
if (creatingError) {
|
|
62
|
-
setError('name', {
|
|
63
|
-
message: creatingError?.message,
|
|
64
|
-
})
|
|
65
|
-
}
|
|
66
|
-
}, [creatingError, setError])
|
|
67
|
-
|
|
68
|
-
const Footer = () => (
|
|
69
|
-
<Box padding={3}>
|
|
70
|
-
<Flex justify="flex-end">
|
|
71
|
-
{/* Submit button */}
|
|
72
|
-
<FormSubmitButton
|
|
73
|
-
disabled={formUpdating || !isDirty || !isValid}
|
|
74
|
-
isValid={isValid}
|
|
75
|
-
onClick={handleSubmit(onSubmit)}
|
|
76
|
-
/>
|
|
77
|
-
</Flex>
|
|
78
|
-
</Box>
|
|
79
|
-
)
|
|
80
|
-
|
|
81
|
-
return (
|
|
82
|
-
<Dialog animate footer={<Footer />} header="Create Tag" id={id} onClose={handleClose} width={1}>
|
|
83
|
-
{/* Form fields */}
|
|
84
|
-
<Box as="form" padding={4} onSubmit={handleSubmit(onSubmit)}>
|
|
85
|
-
{/* Hidden button to enable enter key submissions */}
|
|
86
|
-
<button style={{display: 'none'}} tabIndex={-1} type="submit" />
|
|
87
|
-
|
|
88
|
-
{/* Title */}
|
|
89
|
-
<FormFieldInputText
|
|
90
|
-
{...register('name')}
|
|
91
|
-
disabled={formUpdating}
|
|
92
|
-
error={errors?.name?.message}
|
|
93
|
-
label="Name"
|
|
94
|
-
name="name"
|
|
95
|
-
/>
|
|
96
|
-
</Box>
|
|
97
|
-
|
|
98
|
-
{children}
|
|
99
|
-
</Dialog>
|
|
100
|
-
)
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
export default DialogTagCreate
|
|
@@ -1,165 +0,0 @@
|
|
|
1
|
-
import {fireEvent, screen, waitFor} from '@testing-library/react'
|
|
2
|
-
import userEvent from '@testing-library/user-event'
|
|
3
|
-
import {Subject} from 'rxjs'
|
|
4
|
-
import {describe, expect, it, vi} from 'vitest'
|
|
5
|
-
|
|
6
|
-
import {createMockSanityClient} from '../../__tests__/fixtures/mockSanityClient'
|
|
7
|
-
import {renderWithProviders} from '../../__tests__/fixtures/renderWithProviders'
|
|
8
|
-
import {createTestRootState} from '../../__tests__/fixtures/rootState'
|
|
9
|
-
import {inputByName, withinDialog} from '../../__tests__/fixtures/withinDialog'
|
|
10
|
-
import {tagsActions} from '../../modules/tags'
|
|
11
|
-
import type {Tag} from '../../types'
|
|
12
|
-
import DialogTagEdit from './index'
|
|
13
|
-
|
|
14
|
-
const tag: Tag = {
|
|
15
|
-
_id: 't1',
|
|
16
|
-
_type: 'media.tag',
|
|
17
|
-
_createdAt: '',
|
|
18
|
-
_updatedAt: '',
|
|
19
|
-
_rev: 'r1',
|
|
20
|
-
name: {_type: 'slug', current: 'alpha'},
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
const tagsPreloaded = {
|
|
24
|
-
allIds: ['t1'],
|
|
25
|
-
byIds: {
|
|
26
|
-
t1: {_type: 'tag' as const, tag, picked: false, updating: false},
|
|
27
|
-
},
|
|
28
|
-
creating: false,
|
|
29
|
-
fetchCount: -1,
|
|
30
|
-
fetching: false,
|
|
31
|
-
panelVisible: true,
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
vi.mock('../../hooks/useVersionedClient', () => ({
|
|
35
|
-
default: () =>
|
|
36
|
-
createMockSanityClient({
|
|
37
|
-
listen: vi.fn(() => new Subject()),
|
|
38
|
-
}),
|
|
39
|
-
}))
|
|
40
|
-
|
|
41
|
-
describe('DialogTagEdit', () => {
|
|
42
|
-
it('dispatches updateRequest when name changes and form submits', async () => {
|
|
43
|
-
const user = userEvent.setup()
|
|
44
|
-
const {store} = renderWithProviders(
|
|
45
|
-
<DialogTagEdit dialog={{id: 'dlg-1', type: 'tagEdit', tagId: 't1'}}>
|
|
46
|
-
<span />
|
|
47
|
-
</DialogTagEdit>,
|
|
48
|
-
{
|
|
49
|
-
preloaded: {
|
|
50
|
-
tags: tagsPreloaded,
|
|
51
|
-
},
|
|
52
|
-
},
|
|
53
|
-
)
|
|
54
|
-
|
|
55
|
-
const dlg = withinDialog(/edit tag/i, screen)
|
|
56
|
-
const input = inputByName(/edit tag/i, screen, 'name')
|
|
57
|
-
await user.clear(input)
|
|
58
|
-
await user.type(input, 'beta')
|
|
59
|
-
await user.click(dlg.getByRole('button', {name: /save and close/i}))
|
|
60
|
-
|
|
61
|
-
expect(store.getState().tags.byIds.t1.updating).toBe(true)
|
|
62
|
-
})
|
|
63
|
-
|
|
64
|
-
it('dispatches updateRequest with slug-shaped form data', async () => {
|
|
65
|
-
const user = userEvent.setup()
|
|
66
|
-
const {store} = renderWithProviders(
|
|
67
|
-
<DialogTagEdit dialog={{id: 'dlg-1', type: 'tagEdit', tagId: 't1'}}>
|
|
68
|
-
<span />
|
|
69
|
-
</DialogTagEdit>,
|
|
70
|
-
{
|
|
71
|
-
preloaded: {
|
|
72
|
-
tags: tagsPreloaded,
|
|
73
|
-
},
|
|
74
|
-
},
|
|
75
|
-
)
|
|
76
|
-
const dispatchSpy = vi.spyOn(store, 'dispatch')
|
|
77
|
-
const dlg = withinDialog(/edit tag/i, screen)
|
|
78
|
-
|
|
79
|
-
const input = inputByName(/edit tag/i, screen, 'name')
|
|
80
|
-
await user.clear(input)
|
|
81
|
-
await user.type(input, 'gamma')
|
|
82
|
-
await user.click(dlg.getByRole('button', {name: /save and close/i}))
|
|
83
|
-
|
|
84
|
-
await waitFor(() => {
|
|
85
|
-
let updateAction
|
|
86
|
-
for (const call of dispatchSpy.mock.calls) {
|
|
87
|
-
const action = call[0]
|
|
88
|
-
if (tagsActions.updateRequest.match(action)) {
|
|
89
|
-
updateAction = action
|
|
90
|
-
break
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
expect(updateAction).toBeDefined()
|
|
94
|
-
expect(updateAction?.payload).toMatchObject({
|
|
95
|
-
closeDialogId: 't1',
|
|
96
|
-
formData: {
|
|
97
|
-
name: {_type: 'slug', current: 'gamma'},
|
|
98
|
-
},
|
|
99
|
-
tag,
|
|
100
|
-
})
|
|
101
|
-
})
|
|
102
|
-
})
|
|
103
|
-
|
|
104
|
-
it('does not enable Save until the name is edited', () => {
|
|
105
|
-
renderWithProviders(
|
|
106
|
-
<DialogTagEdit dialog={{id: 'dlg-1', type: 'tagEdit', tagId: 't1'}}>
|
|
107
|
-
<span />
|
|
108
|
-
</DialogTagEdit>,
|
|
109
|
-
{
|
|
110
|
-
preloaded: {
|
|
111
|
-
tags: tagsPreloaded,
|
|
112
|
-
},
|
|
113
|
-
},
|
|
114
|
-
)
|
|
115
|
-
|
|
116
|
-
const dlg = withinDialog(/edit tag/i, screen)
|
|
117
|
-
expect(dlg.getByRole('button', {name: /save and close/i})).toBeDisabled()
|
|
118
|
-
})
|
|
119
|
-
|
|
120
|
-
it('removes only this dialog when closed', async () => {
|
|
121
|
-
const user = userEvent.setup()
|
|
122
|
-
const base = createTestRootState({
|
|
123
|
-
dialog: {
|
|
124
|
-
items: [
|
|
125
|
-
{id: 'dlg-1', type: 'tagEdit', tagId: 't1'},
|
|
126
|
-
{id: 'tags', type: 'tags'},
|
|
127
|
-
],
|
|
128
|
-
},
|
|
129
|
-
tags: tagsPreloaded,
|
|
130
|
-
})
|
|
131
|
-
|
|
132
|
-
const {store} = renderWithProviders(
|
|
133
|
-
<DialogTagEdit dialog={{id: 'dlg-1', type: 'tagEdit', tagId: 't1'}}>
|
|
134
|
-
<span />
|
|
135
|
-
</DialogTagEdit>,
|
|
136
|
-
{preloaded: base},
|
|
137
|
-
)
|
|
138
|
-
|
|
139
|
-
const dlg = withinDialog(/edit tag/i, screen)
|
|
140
|
-
await user.click(dlg.getByRole('button', {name: /close dialog/i}))
|
|
141
|
-
|
|
142
|
-
expect(store.getState().dialog.items).toEqual([{id: 'tags', type: 'tags'}])
|
|
143
|
-
})
|
|
144
|
-
|
|
145
|
-
it('opens the delete confirmation dialog when Delete is clicked', async () => {
|
|
146
|
-
const {store} = renderWithProviders(
|
|
147
|
-
<DialogTagEdit dialog={{id: 'dlg-1', type: 'tagEdit', tagId: 't1'}}>
|
|
148
|
-
<span />
|
|
149
|
-
</DialogTagEdit>,
|
|
150
|
-
{
|
|
151
|
-
preloaded: {
|
|
152
|
-
tags: tagsPreloaded,
|
|
153
|
-
},
|
|
154
|
-
},
|
|
155
|
-
)
|
|
156
|
-
|
|
157
|
-
const dlg = withinDialog(/edit tag/i, screen)
|
|
158
|
-
fireEvent.click(dlg.getByRole('button', {name: /^delete$/i}))
|
|
159
|
-
|
|
160
|
-
const confirm = store.getState().dialog.items.find((d) => d.type === 'confirm')
|
|
161
|
-
expect(confirm).toBeDefined()
|
|
162
|
-
expect(confirm?.title).toMatch(/permanently delete/i)
|
|
163
|
-
expect(confirm?.headerTitle).toBe('Confirm deletion')
|
|
164
|
-
})
|
|
165
|
-
})
|
|
@@ -1,190 +0,0 @@
|
|
|
1
|
-
import type {MutationEvent} from '@sanity/client'
|
|
2
|
-
import {Box, Button, Card, Flex, Text} from '@sanity/ui'
|
|
3
|
-
import groq from 'groq'
|
|
4
|
-
import {type ReactNode, useCallback, useEffect, useState} from 'react'
|
|
5
|
-
import {type SubmitHandler, useForm} from 'react-hook-form'
|
|
6
|
-
import {useDispatch} from 'react-redux'
|
|
7
|
-
|
|
8
|
-
import {tagFormSchema} from '../../formSchema'
|
|
9
|
-
import useTypedSelector from '../../hooks/useTypedSelector'
|
|
10
|
-
import useVersionedClient from '../../hooks/useVersionedClient'
|
|
11
|
-
import {dialogActions} from '../../modules/dialog'
|
|
12
|
-
import {selectTagById, tagsActions} from '../../modules/tags'
|
|
13
|
-
import type {DialogTagEditProps, Tag, TagFormData} from '../../types'
|
|
14
|
-
import sanitizeFormData from '../../utils/sanitizeFormData'
|
|
15
|
-
import zodFormResolver from '../../utils/zodFormResolver'
|
|
16
|
-
import Dialog from '../Dialog'
|
|
17
|
-
import FormFieldInputText from '../FormFieldInputText'
|
|
18
|
-
import FormSubmitButton from '../FormSubmitButton'
|
|
19
|
-
|
|
20
|
-
type Props = {
|
|
21
|
-
children: ReactNode
|
|
22
|
-
dialog: DialogTagEditProps
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
const DialogTagEdit = (props: Props) => {
|
|
26
|
-
const {
|
|
27
|
-
children,
|
|
28
|
-
dialog: {id, tagId},
|
|
29
|
-
} = props
|
|
30
|
-
|
|
31
|
-
const client = useVersionedClient()
|
|
32
|
-
|
|
33
|
-
const dispatch = useDispatch()
|
|
34
|
-
const tagItem = useTypedSelector((state) => selectTagById(state, String(tagId))) // TODO: double check string cast
|
|
35
|
-
|
|
36
|
-
// - Generate a snapshot of the current tag
|
|
37
|
-
const [tagSnapshot, setTagSnapshot] = useState(tagItem?.tag)
|
|
38
|
-
|
|
39
|
-
const currentTag = tagItem ? tagItem?.tag : tagSnapshot
|
|
40
|
-
const generateDefaultValues = (tag?: Tag) => ({
|
|
41
|
-
name: tag?.name?.current || '',
|
|
42
|
-
})
|
|
43
|
-
|
|
44
|
-
const {
|
|
45
|
-
// Read the formState before render to subscribe the form state through Proxy
|
|
46
|
-
formState: {errors, isDirty, isValid},
|
|
47
|
-
handleSubmit,
|
|
48
|
-
register,
|
|
49
|
-
reset,
|
|
50
|
-
setError,
|
|
51
|
-
} = useForm<TagFormData>({
|
|
52
|
-
defaultValues: generateDefaultValues(tagItem?.tag),
|
|
53
|
-
mode: 'onChange',
|
|
54
|
-
resolver: zodFormResolver<TagFormData>(tagFormSchema),
|
|
55
|
-
})
|
|
56
|
-
|
|
57
|
-
const formUpdating = !tagItem || tagItem?.updating
|
|
58
|
-
|
|
59
|
-
const handleClose = () => {
|
|
60
|
-
dispatch(dialogActions.remove({id}))
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// Submit react-hook-form
|
|
64
|
-
const onSubmit: SubmitHandler<TagFormData> = (formData) => {
|
|
65
|
-
if (!tagItem?.tag) {
|
|
66
|
-
return
|
|
67
|
-
}
|
|
68
|
-
const sanitizedFormData = sanitizeFormData(formData)
|
|
69
|
-
dispatch(
|
|
70
|
-
tagsActions.updateRequest({
|
|
71
|
-
closeDialogId: tagItem?.tag?._id,
|
|
72
|
-
formData: {
|
|
73
|
-
name: {
|
|
74
|
-
_type: 'slug',
|
|
75
|
-
current: sanitizedFormData.name,
|
|
76
|
-
},
|
|
77
|
-
},
|
|
78
|
-
tag: tagItem?.tag,
|
|
79
|
-
}),
|
|
80
|
-
)
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
const handleDelete = () => {
|
|
84
|
-
if (!tagItem?.tag) {
|
|
85
|
-
return
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
dispatch(
|
|
89
|
-
dialogActions.showConfirmDeleteTag({
|
|
90
|
-
closeDialogId: tagItem?.tag?._id,
|
|
91
|
-
tag: tagItem?.tag,
|
|
92
|
-
}),
|
|
93
|
-
)
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
const handleTagUpdate = useCallback(
|
|
97
|
-
(update: MutationEvent) => {
|
|
98
|
-
const {result, transition} = update
|
|
99
|
-
if (result && transition === 'update') {
|
|
100
|
-
// Regenerate snapshot
|
|
101
|
-
setTagSnapshot(result as Tag)
|
|
102
|
-
// Reset react-hook-form
|
|
103
|
-
reset(generateDefaultValues(result as Tag))
|
|
104
|
-
}
|
|
105
|
-
},
|
|
106
|
-
[reset],
|
|
107
|
-
)
|
|
108
|
-
|
|
109
|
-
useEffect(() => {
|
|
110
|
-
if (tagItem?.error) {
|
|
111
|
-
setError('name', {
|
|
112
|
-
message: tagItem.error?.message,
|
|
113
|
-
})
|
|
114
|
-
}
|
|
115
|
-
}, [setError, tagItem.error])
|
|
116
|
-
|
|
117
|
-
// - Listen for asset mutations and update snapshot
|
|
118
|
-
useEffect(() => {
|
|
119
|
-
if (!tagItem?.tag) {
|
|
120
|
-
return undefined
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// Remember that Sanity listeners ignore joins, order clauses and projections
|
|
124
|
-
const subscriptionAsset = client
|
|
125
|
-
.listen(groq`*[_id == $id]`, {id: tagItem?.tag._id})
|
|
126
|
-
.subscribe(handleTagUpdate)
|
|
127
|
-
|
|
128
|
-
return () => {
|
|
129
|
-
subscriptionAsset?.unsubscribe()
|
|
130
|
-
}
|
|
131
|
-
}, [client, handleTagUpdate, tagItem?.tag])
|
|
132
|
-
|
|
133
|
-
const Footer = () => (
|
|
134
|
-
<Box padding={3}>
|
|
135
|
-
<Flex justify="space-between">
|
|
136
|
-
{/* Delete button */}
|
|
137
|
-
<Button
|
|
138
|
-
disabled={formUpdating}
|
|
139
|
-
fontSize={1}
|
|
140
|
-
mode="bleed"
|
|
141
|
-
onClick={handleDelete}
|
|
142
|
-
text="Delete"
|
|
143
|
-
tone="critical"
|
|
144
|
-
/>
|
|
145
|
-
|
|
146
|
-
{/* Submit button */}
|
|
147
|
-
<FormSubmitButton
|
|
148
|
-
disabled={formUpdating || !isDirty || !isValid}
|
|
149
|
-
isValid={isValid}
|
|
150
|
-
lastUpdated={tagItem?.tag?._updatedAt}
|
|
151
|
-
onClick={handleSubmit(onSubmit)}
|
|
152
|
-
/>
|
|
153
|
-
</Flex>
|
|
154
|
-
</Box>
|
|
155
|
-
)
|
|
156
|
-
|
|
157
|
-
if (!currentTag) {
|
|
158
|
-
return null
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
return (
|
|
162
|
-
<Dialog animate footer={<Footer />} header="Edit Tag" id={id} onClose={handleClose} width={1}>
|
|
163
|
-
{/* Form fields */}
|
|
164
|
-
<Box as="form" padding={4} onSubmit={handleSubmit(onSubmit)}>
|
|
165
|
-
{/* Deleted notification */}
|
|
166
|
-
{!tagItem && (
|
|
167
|
-
<Card marginBottom={3} padding={3} radius={2} shadow={1} tone="critical">
|
|
168
|
-
<Text size={1}>This tag cannot be found – it may have been deleted.</Text>
|
|
169
|
-
</Card>
|
|
170
|
-
)}
|
|
171
|
-
|
|
172
|
-
{/* Hidden button to enable enter key submissions */}
|
|
173
|
-
<button style={{display: 'none'}} tabIndex={-1} type="submit" />
|
|
174
|
-
|
|
175
|
-
{/* Title */}
|
|
176
|
-
<FormFieldInputText
|
|
177
|
-
{...register('name')}
|
|
178
|
-
disabled={formUpdating}
|
|
179
|
-
error={errors?.name?.message}
|
|
180
|
-
label="Name"
|
|
181
|
-
name="name"
|
|
182
|
-
/>
|
|
183
|
-
</Box>
|
|
184
|
-
|
|
185
|
-
{children}
|
|
186
|
-
</Dialog>
|
|
187
|
-
)
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
export default DialogTagEdit
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import {Box} from '@sanity/ui'
|
|
2
|
-
import {type ReactNode, useCallback} from 'react'
|
|
3
|
-
import {useDispatch} from 'react-redux'
|
|
4
|
-
|
|
5
|
-
import {dialogActions} from '../../modules/dialog'
|
|
6
|
-
import type {DialogTagsProps} from '../../types'
|
|
7
|
-
import Dialog from '../Dialog'
|
|
8
|
-
import TagView from '../TagView'
|
|
9
|
-
|
|
10
|
-
type Props = {
|
|
11
|
-
children: ReactNode
|
|
12
|
-
dialog: DialogTagsProps
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
const DialogTags = (props: Props) => {
|
|
16
|
-
const {
|
|
17
|
-
children,
|
|
18
|
-
dialog: {id},
|
|
19
|
-
} = props
|
|
20
|
-
|
|
21
|
-
// Redux
|
|
22
|
-
const dispatch = useDispatch()
|
|
23
|
-
|
|
24
|
-
// Callbacks
|
|
25
|
-
const handleClose = useCallback(() => {
|
|
26
|
-
dispatch(dialogActions.clear())
|
|
27
|
-
}, [])
|
|
28
|
-
|
|
29
|
-
return (
|
|
30
|
-
<Dialog animate header="All Tags" id={id} onClose={handleClose} width={1}>
|
|
31
|
-
<Box
|
|
32
|
-
style={{
|
|
33
|
-
height: '100%',
|
|
34
|
-
minHeight: '420px', // explicit height required as <TagView> is virtualized
|
|
35
|
-
}}
|
|
36
|
-
>
|
|
37
|
-
<TagView />
|
|
38
|
-
</Box>
|
|
39
|
-
|
|
40
|
-
{children}
|
|
41
|
-
</Dialog>
|
|
42
|
-
)
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export default DialogTags
|
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
import useTypedSelector from '../../hooks/useTypedSelector'
|
|
2
|
-
import type {Dialog} from '../../types'
|
|
3
|
-
import DialogAssetEdit from '../DialogAssetEdit'
|
|
4
|
-
import DialogConfirm from '../DialogConfirm'
|
|
5
|
-
import DialogSearchFacets from '../DialogSearchFacets'
|
|
6
|
-
import DialogTagCreate from '../DialogTagCreate'
|
|
7
|
-
import DialogTagEdit from '../DialogTagEdit'
|
|
8
|
-
import DialogTags from '../DialogTags'
|
|
9
|
-
|
|
10
|
-
const Dialogs = () => {
|
|
11
|
-
// Redux
|
|
12
|
-
const currentDialogs = useTypedSelector((state) => state.dialog.items)
|
|
13
|
-
|
|
14
|
-
const renderDialogs = (dialogs: Dialog[], index: number) => {
|
|
15
|
-
if (dialogs.length === 0 || index >= dialogs.length) {
|
|
16
|
-
return null
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
const dialog = dialogs[index]
|
|
20
|
-
const childDialogs = renderDialogs(dialogs, index + 1)
|
|
21
|
-
|
|
22
|
-
if (dialog.type === 'assetEdit') {
|
|
23
|
-
return (
|
|
24
|
-
<DialogAssetEdit dialog={dialog} key={index}>
|
|
25
|
-
{childDialogs}
|
|
26
|
-
</DialogAssetEdit>
|
|
27
|
-
)
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
if (dialog.type === 'confirm') {
|
|
31
|
-
return (
|
|
32
|
-
<DialogConfirm dialog={dialog} key={index}>
|
|
33
|
-
{childDialogs}
|
|
34
|
-
</DialogConfirm>
|
|
35
|
-
)
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
if (dialog.type === 'searchFacets') {
|
|
39
|
-
return (
|
|
40
|
-
<DialogSearchFacets dialog={dialog} key={index}>
|
|
41
|
-
{childDialogs}
|
|
42
|
-
</DialogSearchFacets>
|
|
43
|
-
)
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
if (dialog.type === 'tagCreate') {
|
|
47
|
-
return (
|
|
48
|
-
<DialogTagCreate dialog={dialog} key={index}>
|
|
49
|
-
{childDialogs}
|
|
50
|
-
</DialogTagCreate>
|
|
51
|
-
)
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
if (dialog.type === 'tagEdit') {
|
|
55
|
-
return (
|
|
56
|
-
<DialogTagEdit dialog={dialog} key={index}>
|
|
57
|
-
{childDialogs}
|
|
58
|
-
</DialogTagEdit>
|
|
59
|
-
)
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
if (dialog.type === 'tags') {
|
|
63
|
-
return (
|
|
64
|
-
<DialogTags dialog={dialog} key={index}>
|
|
65
|
-
{childDialogs}
|
|
66
|
-
</DialogTags>
|
|
67
|
-
)
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
return null
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
return renderDialogs(currentDialogs, 0)
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
export default Dialogs
|