sanity-plugin-media 4.3.1 → 4.3.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/LICENSE +4 -4
- package/README.md +12 -12
- package/dist/index.d.mts +263 -195
- package/dist/index.d.ts +263 -195
- package/dist/index.js +83 -203
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +84 -203
- package/dist/index.mjs.map +1 -1
- package/package.json +41 -63
- package/src/__tests__/fixtures/createEpicTestStore.ts +5 -4
- package/src/__tests__/fixtures/mockSanityClient.ts +8 -8
- package/src/__tests__/fixtures/renderWithProviders.tsx +8 -7
- package/src/__tests__/fixtures/rootState.ts +4 -4
- package/src/components/AssetGridVirtualized/index.tsx +8 -7
- package/src/components/AssetMetadata/index.tsx +6 -5
- package/src/components/AssetTableVirtualized/index.tsx +7 -6
- package/src/components/AutoTagInputWrapper/index.tsx +9 -4
- package/src/components/Browser/Browser.test.tsx +9 -8
- package/src/components/Browser/index.tsx +2 -1
- package/src/components/Browser/useBrowserInit.ts +9 -9
- package/src/components/ButtonAssetCopy/index.tsx +1 -0
- package/src/components/ButtonViewGroup/index.tsx +4 -3
- package/src/components/CardAsset/CardAsset.test.tsx +53 -52
- package/src/components/CardAsset/index.tsx +52 -49
- package/src/components/CardUpload/index.tsx +7 -6
- package/src/components/Controls/index.tsx +7 -6
- package/src/components/DebugControls/index.tsx +5 -4
- package/src/components/DialogAssetEdit/Details.tsx +3 -2
- package/src/components/DialogAssetEdit/DialogAssetEdit.test.tsx +28 -27
- package/src/components/DialogAssetEdit/index.tsx +37 -37
- package/src/components/DialogConfirm/index.tsx +2 -1
- package/src/components/DialogSearchFacets/index.tsx +3 -2
- package/src/components/DialogTagCreate/DialogTagCreate.test.tsx +16 -15
- package/src/components/DialogTagCreate/index.tsx +11 -10
- package/src/components/DialogTagEdit/DialogTagEdit.test.tsx +28 -27
- package/src/components/DialogTagEdit/index.tsx +17 -16
- package/src/components/DialogTags/index.tsx +4 -3
- package/src/components/Dialogs/index.tsx +2 -3
- package/src/components/DocumentList/index.tsx +2 -3
- package/src/components/FileAssetPreview/index.tsx +2 -2
- package/src/components/FormBuilderTool/FormBuilderTool.test.tsx +12 -11
- package/src/components/FormBuilderTool/index.tsx +2 -1
- package/src/components/FormFieldInputLabel/index.tsx +1 -2
- package/src/components/FormFieldInputTags/index.tsx +4 -3
- package/src/components/FormSubmitButton/index.tsx +1 -1
- package/src/components/Header/index.tsx +3 -3
- package/src/components/Image/index.tsx +10 -4
- package/src/components/Items/index.tsx +5 -4
- package/src/components/Notifications/index.tsx +3 -2
- package/src/components/OrderSelect/index.tsx +4 -3
- package/src/components/PickedBar/index.tsx +2 -1
- package/src/components/Progress/index.tsx +3 -3
- package/src/components/ReduxProvider/index.tsx +15 -12
- package/src/components/SearchFacet/index.tsx +3 -2
- package/src/components/SearchFacetNumber/index.tsx +8 -8
- package/src/components/SearchFacetSelect/index.tsx +7 -8
- package/src/components/SearchFacetString/index.tsx +1 -1
- package/src/components/SearchFacetTags/index.tsx +13 -12
- package/src/components/SearchFacets/index.tsx +2 -3
- package/src/components/SearchFacetsControl/index.tsx +13 -12
- package/src/components/TableHeader/index.tsx +18 -17
- package/src/components/TableHeaderItem/index.tsx +4 -4
- package/src/components/TableRowAsset/index.tsx +37 -36
- package/src/components/TableRowUpload/index.tsx +7 -6
- package/src/components/Tag/index.tsx +8 -7
- package/src/components/TagView/index.tsx +2 -2
- package/src/components/TagViewHeader/index.tsx +5 -4
- package/src/components/TagsPanel/index.tsx +3 -3
- package/src/components/TagsVirtualized/index.tsx +25 -24
- package/src/components/TextInputSearch/index.tsx +3 -2
- package/src/components/UploadDropzone/UploadDropzone.test.tsx +8 -7
- package/src/components/UploadDropzone/index.tsx +14 -13
- package/src/config/orders.ts +6 -6
- package/src/config/searchFacets.ts +56 -55
- package/src/constants.ts +15 -14
- package/src/contexts/AssetSourceDispatchContext.tsx +1 -1
- package/src/contexts/ToolOptionsContext.tsx +6 -5
- package/src/formSchema/index.test.ts +6 -5
- package/src/formSchema/index.ts +5 -5
- package/src/hooks/useBreakpointIndex.ts +6 -6
- package/src/hooks/useKeyPress.ts +2 -2
- package/src/hooks/usePortalPopoverProps.ts +1 -1
- package/src/modules/assets/actions.ts +8 -7
- package/src/modules/assets/deleteAndUpdateEpics.test.ts +18 -17
- package/src/modules/assets/fetchEpic.test.ts +12 -11
- package/src/modules/assets/index.ts +134 -133
- package/src/modules/assets/reducer.test.ts +9 -8
- package/src/modules/assets/tagsAndListenerEpics.test.ts +36 -35
- package/src/modules/debug/index.ts +3 -3
- package/src/modules/dialog/actions.ts +2 -2
- package/src/modules/dialog/epics.test.ts +29 -28
- package/src/modules/dialog/index.ts +36 -35
- package/src/modules/dialog/reducer.test.ts +31 -30
- package/src/modules/index.ts +9 -9
- package/src/modules/notifications/epics.test.ts +71 -70
- package/src/modules/notifications/index.ts +50 -49
- package/src/modules/notifications/reducer.test.ts +8 -7
- package/src/modules/search/index.test.ts +2 -1
- package/src/modules/search/index.ts +22 -22
- package/src/modules/selected/index.ts +2 -2
- package/src/modules/selectors.test.ts +4 -3
- package/src/modules/selectors.ts +5 -5
- package/src/modules/tags/epics.test.ts +16 -15
- package/src/modules/tags/index.test.ts +2 -1
- package/src/modules/tags/index.ts +82 -81
- package/src/modules/uploads/actions.ts +3 -3
- package/src/modules/uploads/epics.test.ts +13 -12
- package/src/modules/uploads/index.test.ts +8 -7
- package/src/modules/uploads/index.ts +48 -47
- package/src/operators/checkTagName.test.ts +7 -6
- package/src/operators/checkTagName.ts +6 -5
- package/src/operators/debugThrottle.ts +4 -4
- package/src/plugin.tsx +18 -18
- package/src/schemas/tag.ts +7 -7
- package/src/styled/react-select/creatable.tsx +40 -39
- package/src/styled/react-select/single.tsx +39 -38
- package/src/types/index.ts +4 -3
- package/src/utils/applyMediaTags.ts +11 -10
- package/src/utils/blocksToText.test.ts +5 -4
- package/src/utils/blocksToText.ts +2 -2
- package/src/utils/constructFilter.test.ts +15 -14
- package/src/utils/constructFilter.ts +7 -7
- package/src/utils/generatePreviewBlobUrl.test.ts +6 -5
- package/src/utils/generatePreviewBlobUrl.ts +2 -2
- package/src/utils/getAssetResolution.test.ts +3 -2
- package/src/utils/getDocumentAssetIds.test.ts +7 -6
- package/src/utils/getDocumentAssetIds.ts +2 -2
- package/src/utils/getSchemeColor.test.ts +1 -0
- package/src/utils/getSchemeColor.ts +9 -9
- package/src/utils/getTagSelectOptions.test.ts +6 -5
- package/src/utils/getTagSelectOptions.ts +1 -1
- package/src/utils/getUniqueDocuments.test.ts +4 -3
- package/src/utils/getUniqueDocuments.ts +2 -2
- package/src/utils/imageDprUrl.test.ts +4 -3
- package/src/utils/imageDprUrl.ts +1 -1
- package/src/utils/isSupportedAssetType.test.ts +1 -0
- package/src/utils/mediaField.ts +4 -3
- package/src/utils/sanitizeFormData.test.ts +14 -13
- package/src/utils/typeGuards.test.ts +2 -1
- package/src/utils/uploadSanityAsset.test.ts +5 -4
- package/src/utils/uploadSanityAsset.ts +17 -16
- package/src/utils/withMaxConcurrency.test.ts +5 -4
- package/src/utils/withMaxConcurrency.ts +4 -4
- package/src/utils/zodFormResolver.ts +17 -0
- package/v2-incompatible.js +2 -2
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import userEvent from '@testing-library/user-event'
|
|
2
1
|
import {fireEvent, screen, waitFor} from '@testing-library/react'
|
|
3
|
-
import
|
|
2
|
+
import userEvent from '@testing-library/user-event'
|
|
4
3
|
import {Subject} from 'rxjs'
|
|
4
|
+
import {describe, expect, it, vi} from 'vitest'
|
|
5
|
+
|
|
5
6
|
import DialogAssetEdit from './index'
|
|
6
7
|
|
|
7
8
|
vi.mock('../Image', () => ({default: () => null}))
|
|
@@ -12,8 +13,8 @@ import {createMockSanityClient} from '../../__tests__/fixtures/mockSanityClient'
|
|
|
12
13
|
import {renderWithProviders} from '../../__tests__/fixtures/renderWithProviders'
|
|
13
14
|
import {createTestRootState} from '../../__tests__/fixtures/rootState'
|
|
14
15
|
import {inputByName, withinDialog} from '../../__tests__/fixtures/withinDialog'
|
|
15
|
-
import type {RootReducerState} from '../../modules/types'
|
|
16
16
|
import {assetsActions, initialState as assetsInitialState} from '../../modules/assets'
|
|
17
|
+
import type {RootReducerState} from '../../modules/types'
|
|
17
18
|
import type {AssetType, ImageAsset, MediaToolOptions} from '../../types'
|
|
18
19
|
|
|
19
20
|
const asset = {
|
|
@@ -26,7 +27,7 @@ const asset = {
|
|
|
26
27
|
size: 1,
|
|
27
28
|
mimeType: 'image/png',
|
|
28
29
|
url: 'https://example.com/x.png',
|
|
29
|
-
metadata: {dimensions: {width: 100, height: 100}, isOpaque: true}
|
|
30
|
+
metadata: {dimensions: {width: 100, height: 100}, isOpaque: true},
|
|
30
31
|
} as ImageAsset
|
|
31
32
|
|
|
32
33
|
const assetsPreloaded = {
|
|
@@ -34,25 +35,25 @@ const assetsPreloaded = {
|
|
|
34
35
|
assetTypes: ['image'] as AssetType[],
|
|
35
36
|
allIds: ['a1'],
|
|
36
37
|
byIds: {
|
|
37
|
-
a1: {_type: 'asset' as const, asset, picked: false, updating: false}
|
|
38
|
-
}
|
|
38
|
+
a1: {_type: 'asset' as const, asset, picked: false, updating: false},
|
|
39
|
+
},
|
|
39
40
|
}
|
|
40
41
|
|
|
41
|
-
vi.mock('sanity', async importOriginal => {
|
|
42
|
+
vi.mock('sanity', async (importOriginal) => {
|
|
42
43
|
const actual = await importOriginal<typeof import('sanity')>()
|
|
43
44
|
return {
|
|
44
45
|
...actual,
|
|
45
46
|
WithReferringDocuments: ({children}: {children: (args: unknown) => unknown}) =>
|
|
46
47
|
children({isLoading: false, referringDocuments: []}),
|
|
47
|
-
useDocumentStore: () => ({})
|
|
48
|
+
useDocumentStore: () => ({}),
|
|
48
49
|
}
|
|
49
50
|
})
|
|
50
51
|
|
|
51
52
|
vi.mock('../../hooks/useVersionedClient', () => ({
|
|
52
53
|
default: () =>
|
|
53
54
|
createMockSanityClient({
|
|
54
|
-
listen: vi.fn(() => new Subject())
|
|
55
|
-
})
|
|
55
|
+
listen: vi.fn(() => new Subject()),
|
|
56
|
+
}),
|
|
56
57
|
}))
|
|
57
58
|
|
|
58
59
|
function renderAssetDialog(
|
|
@@ -60,7 +61,7 @@ function renderAssetDialog(
|
|
|
60
61
|
opts: {
|
|
61
62
|
preloaded?: Partial<RootReducerState>
|
|
62
63
|
toolOptions?: Partial<MediaToolOptions>
|
|
63
|
-
} = {}
|
|
64
|
+
} = {},
|
|
64
65
|
) {
|
|
65
66
|
const {preloaded: extraPreloaded, toolOptions} = opts
|
|
66
67
|
return renderWithProviders(
|
|
@@ -70,10 +71,10 @@ function renderAssetDialog(
|
|
|
70
71
|
{
|
|
71
72
|
preloaded: {
|
|
72
73
|
assets: assetsPreloaded,
|
|
73
|
-
...extraPreloaded
|
|
74
|
+
...extraPreloaded,
|
|
74
75
|
},
|
|
75
|
-
toolOptions: {creditLine: {enabled: true}, ...toolOptions}
|
|
76
|
-
}
|
|
76
|
+
toolOptions: {creditLine: {enabled: true}, ...toolOptions},
|
|
77
|
+
},
|
|
77
78
|
)
|
|
78
79
|
}
|
|
79
80
|
|
|
@@ -82,7 +83,7 @@ describe('DialogAssetEdit', () => {
|
|
|
82
83
|
renderAssetDialog({
|
|
83
84
|
id: 'dlg-1',
|
|
84
85
|
type: 'assetEdit',
|
|
85
|
-
assetId: 'a1'
|
|
86
|
+
assetId: 'a1',
|
|
86
87
|
})
|
|
87
88
|
|
|
88
89
|
const dlg = withinDialog(/asset details/i, screen)
|
|
@@ -94,7 +95,7 @@ describe('DialogAssetEdit', () => {
|
|
|
94
95
|
renderAssetDialog({
|
|
95
96
|
id: 'dlg-1',
|
|
96
97
|
type: 'assetEdit',
|
|
97
|
-
assetId: 'a1'
|
|
98
|
+
assetId: 'a1',
|
|
98
99
|
})
|
|
99
100
|
|
|
100
101
|
const dlg = withinDialog(/asset details/i, screen)
|
|
@@ -106,7 +107,7 @@ describe('DialogAssetEdit', () => {
|
|
|
106
107
|
const {store} = renderAssetDialog({
|
|
107
108
|
id: 'dlg-1',
|
|
108
109
|
type: 'assetEdit',
|
|
109
|
-
assetId: 'a1'
|
|
110
|
+
assetId: 'a1',
|
|
110
111
|
})
|
|
111
112
|
const dispatchSpy = vi.spyOn(store, 'dispatch')
|
|
112
113
|
const dlg = withinDialog(/asset details/i, screen)
|
|
@@ -131,8 +132,8 @@ describe('DialogAssetEdit', () => {
|
|
|
131
132
|
closeDialogId: 'a1',
|
|
132
133
|
formData: expect.objectContaining({
|
|
133
134
|
title: 'Hero image',
|
|
134
|
-
originalFilename: 'x.png'
|
|
135
|
-
})
|
|
135
|
+
originalFilename: 'x.png',
|
|
136
|
+
}),
|
|
136
137
|
})
|
|
137
138
|
})
|
|
138
139
|
})
|
|
@@ -143,10 +144,10 @@ describe('DialogAssetEdit', () => {
|
|
|
143
144
|
dialog: {
|
|
144
145
|
items: [
|
|
145
146
|
{id: 'dlg-1', type: 'assetEdit', assetId: 'a1'},
|
|
146
|
-
{id: 'tags', type: 'tags'}
|
|
147
|
-
]
|
|
147
|
+
{id: 'tags', type: 'tags'},
|
|
148
|
+
],
|
|
148
149
|
},
|
|
149
|
-
assets: assetsPreloaded
|
|
150
|
+
assets: assetsPreloaded,
|
|
150
151
|
})
|
|
151
152
|
|
|
152
153
|
const {store} = renderWithProviders(
|
|
@@ -154,15 +155,15 @@ describe('DialogAssetEdit', () => {
|
|
|
154
155
|
dialog={{
|
|
155
156
|
id: 'dlg-1',
|
|
156
157
|
type: 'assetEdit',
|
|
157
|
-
assetId: 'a1'
|
|
158
|
+
assetId: 'a1',
|
|
158
159
|
}}
|
|
159
160
|
>
|
|
160
161
|
<span />
|
|
161
162
|
</DialogAssetEdit>,
|
|
162
163
|
{
|
|
163
164
|
preloaded: base,
|
|
164
|
-
toolOptions: {creditLine: {enabled: true}}
|
|
165
|
-
}
|
|
165
|
+
toolOptions: {creditLine: {enabled: true}},
|
|
166
|
+
},
|
|
166
167
|
)
|
|
167
168
|
|
|
168
169
|
const dlg = withinDialog(/asset details/i, screen)
|
|
@@ -175,7 +176,7 @@ describe('DialogAssetEdit', () => {
|
|
|
175
176
|
const {store} = renderAssetDialog({
|
|
176
177
|
id: 'dlg-1',
|
|
177
178
|
type: 'assetEdit',
|
|
178
|
-
assetId: 'a1'
|
|
179
|
+
assetId: 'a1',
|
|
179
180
|
})
|
|
180
181
|
|
|
181
182
|
const dlg = withinDialog(/asset details/i, screen)
|
|
@@ -200,7 +201,7 @@ describe('DialogAssetEdit', () => {
|
|
|
200
201
|
renderAssetDialog({
|
|
201
202
|
id: 'dlg-1',
|
|
202
203
|
type: 'assetEdit',
|
|
203
|
-
assetId: 'a1'
|
|
204
|
+
assetId: 'a1',
|
|
204
205
|
})
|
|
205
206
|
|
|
206
207
|
const dlg = withinDialog(/asset details/i, screen)
|
|
@@ -1,30 +1,31 @@
|
|
|
1
|
-
import {zodResolver} from '@hookform/resolvers/zod'
|
|
2
1
|
import type {MutationEvent} from '@sanity/client'
|
|
3
2
|
import {Box, Button, Card, Flex, Stack, Tab, TabList, TabPanel, Text} from '@sanity/ui'
|
|
4
|
-
import type {Asset, AssetFormData, DialogAssetEditProps, TagSelectOption} from '../../types'
|
|
5
3
|
import groq from 'groq'
|
|
6
4
|
import {type ReactNode, useCallback, useEffect, useMemo, useRef, useState} from 'react'
|
|
7
5
|
import {type SubmitHandler, useForm} from 'react-hook-form'
|
|
8
6
|
import {useDispatch} from 'react-redux'
|
|
9
7
|
import {WithReferringDocuments, useColorSchemeValue, useDocumentStore} from 'sanity'
|
|
8
|
+
|
|
9
|
+
import {useToolOptions} from '../../contexts/ToolOptionsContext'
|
|
10
10
|
import {getAssetFormSchema} from '../../formSchema'
|
|
11
11
|
import useTypedSelector from '../../hooks/useTypedSelector'
|
|
12
12
|
import useVersionedClient from '../../hooks/useVersionedClient'
|
|
13
13
|
import {assetsActions, selectAssetById} from '../../modules/assets'
|
|
14
14
|
import {dialogActions} from '../../modules/dialog'
|
|
15
15
|
import {selectTags, selectTagSelectOptions, tagsActions} from '../../modules/tags'
|
|
16
|
+
import type {Asset, AssetFormData, DialogAssetEditProps, TagSelectOption} from '../../types'
|
|
16
17
|
import getTagSelectOptions from '../../utils/getTagSelectOptions'
|
|
17
18
|
import {getUniqueDocuments} from '../../utils/getUniqueDocuments'
|
|
18
19
|
import imageDprUrl from '../../utils/imageDprUrl'
|
|
19
20
|
import sanitizeFormData from '../../utils/sanitizeFormData'
|
|
20
21
|
import {isFileAsset, isImageAsset} from '../../utils/typeGuards'
|
|
22
|
+
import zodFormResolver from '../../utils/zodFormResolver'
|
|
21
23
|
import AssetMetadata from '../AssetMetadata'
|
|
22
24
|
import Dialog from '../Dialog'
|
|
23
25
|
import DocumentList from '../DocumentList'
|
|
24
26
|
import FileAssetPreview from '../FileAssetPreview'
|
|
25
27
|
import FormSubmitButton from '../FormSubmitButton'
|
|
26
28
|
import Image from '../Image'
|
|
27
|
-
import {useToolOptions} from '../../contexts/ToolOptionsContext'
|
|
28
29
|
import Details, {type DetailsProps} from './Details'
|
|
29
30
|
|
|
30
31
|
function renderDefaultDetails(props: DetailsProps) {
|
|
@@ -39,7 +40,7 @@ type Props = {
|
|
|
39
40
|
const DialogAssetEdit = (props: Props) => {
|
|
40
41
|
const {
|
|
41
42
|
children,
|
|
42
|
-
dialog: {assetId, id, lastCreatedTag, lastRemovedTagIds}
|
|
43
|
+
dialog: {assetId, id, lastCreatedTag, lastRemovedTagIds},
|
|
43
44
|
} = props
|
|
44
45
|
|
|
45
46
|
const client = useVersionedClient()
|
|
@@ -48,7 +49,7 @@ const DialogAssetEdit = (props: Props) => {
|
|
|
48
49
|
const documentStore = useDocumentStore()
|
|
49
50
|
|
|
50
51
|
const dispatch = useDispatch()
|
|
51
|
-
const assetItem = useTypedSelector(state => selectAssetById(state, String(assetId))) // TODO: check casting
|
|
52
|
+
const assetItem = useTypedSelector((state) => selectAssetById(state, String(assetId))) // TODO: check casting
|
|
52
53
|
const tags = useTypedSelector(selectTags)
|
|
53
54
|
|
|
54
55
|
const assetUpdatedPrev = useRef<string | undefined>(undefined)
|
|
@@ -90,7 +91,7 @@ const DialogAssetEdit = (props: Props) => {
|
|
|
90
91
|
description: makeLocaleObj(asset?.description),
|
|
91
92
|
originalFilename: asset?.originalFilename || '',
|
|
92
93
|
opt: {media: {tags: assetTagOptions}},
|
|
93
|
-
title: makeLocaleObj(asset?.title)
|
|
94
|
+
title: makeLocaleObj(asset?.title),
|
|
94
95
|
}
|
|
95
96
|
}
|
|
96
97
|
// Normalize: if a field is a localized object but locales are disabled, pick first non-empty value
|
|
@@ -98,7 +99,7 @@ const DialogAssetEdit = (props: Props) => {
|
|
|
98
99
|
if (typeof field === 'string') return field
|
|
99
100
|
if (typeof field === 'object' && field !== null) {
|
|
100
101
|
const values = Object.values(field as Record<string, string>)
|
|
101
|
-
return values.find(v => v) || ''
|
|
102
|
+
return values.find((v) => v) || ''
|
|
102
103
|
}
|
|
103
104
|
return ''
|
|
104
105
|
}
|
|
@@ -108,10 +109,10 @@ const DialogAssetEdit = (props: Props) => {
|
|
|
108
109
|
description: flattenField(asset?.description),
|
|
109
110
|
originalFilename: asset?.originalFilename || '',
|
|
110
111
|
opt: {media: {tags: assetTagOptions}},
|
|
111
|
-
title: flattenField(asset?.title)
|
|
112
|
+
title: flattenField(asset?.title),
|
|
112
113
|
}
|
|
113
114
|
},
|
|
114
|
-
[assetTagOptions, locales]
|
|
115
|
+
[assetTagOptions, locales],
|
|
115
116
|
)
|
|
116
117
|
|
|
117
118
|
const {
|
|
@@ -122,11 +123,11 @@ const DialogAssetEdit = (props: Props) => {
|
|
|
122
123
|
handleSubmit,
|
|
123
124
|
register,
|
|
124
125
|
reset,
|
|
125
|
-
setValue
|
|
126
|
+
setValue,
|
|
126
127
|
} = useForm<AssetFormData>({
|
|
127
128
|
defaultValues: generateDefaultValues(assetItem?.asset),
|
|
128
129
|
mode: 'onChange',
|
|
129
|
-
resolver:
|
|
130
|
+
resolver: zodFormResolver<AssetFormData>(getAssetFormSchema(locales)),
|
|
130
131
|
})
|
|
131
132
|
|
|
132
133
|
const formUpdating = !assetItem || assetItem?.updating
|
|
@@ -143,8 +144,8 @@ const DialogAssetEdit = (props: Props) => {
|
|
|
143
144
|
dispatch(
|
|
144
145
|
dialogActions.showConfirmDeleteAssets({
|
|
145
146
|
assets: [assetItem],
|
|
146
|
-
closeDialogId: assetItem?.asset._id
|
|
147
|
-
})
|
|
147
|
+
closeDialogId: assetItem?.asset._id,
|
|
148
|
+
}),
|
|
148
149
|
)
|
|
149
150
|
}, [assetItem, dispatch])
|
|
150
151
|
|
|
@@ -162,31 +163,30 @@ const DialogAssetEdit = (props: Props) => {
|
|
|
162
163
|
dispatch(
|
|
163
164
|
tagsActions.createRequest({
|
|
164
165
|
assetId: currentAsset?._id,
|
|
165
|
-
name: tagName
|
|
166
|
-
})
|
|
166
|
+
name: tagName,
|
|
167
|
+
}),
|
|
167
168
|
)
|
|
168
169
|
},
|
|
169
|
-
[currentAsset?._id, dispatch]
|
|
170
|
+
[currentAsset?._id, dispatch],
|
|
170
171
|
)
|
|
171
172
|
|
|
172
173
|
// Detect if asset has localized fields (objects) with keys not in the configured locales
|
|
173
174
|
const hasOrphanedLocales = useMemo(() => {
|
|
174
175
|
if (!currentAsset) return false
|
|
175
|
-
const isLocaleObj = (v: unknown) =>
|
|
176
|
-
typeof v === 'object' && v !== null && !Array.isArray(v)
|
|
176
|
+
const isLocaleObj = (v: unknown) => typeof v === 'object' && v !== null && !Array.isArray(v)
|
|
177
177
|
const fields = [
|
|
178
178
|
currentAsset.title,
|
|
179
179
|
currentAsset.altText,
|
|
180
180
|
currentAsset.description,
|
|
181
|
-
...(currentAsset._type === 'sanity.imageAsset' ? [currentAsset.creditLine] : [])
|
|
181
|
+
...(currentAsset._type === 'sanity.imageAsset' ? [currentAsset.creditLine] : []),
|
|
182
182
|
]
|
|
183
|
-
const anyLocalized = fields.some(f => isLocaleObj(f))
|
|
183
|
+
const anyLocalized = fields.some((f) => isLocaleObj(f))
|
|
184
184
|
if (!anyLocalized) return false
|
|
185
185
|
if (!locales || locales.length === 0) return true
|
|
186
|
-
const configuredIds = new Set(locales.map(l => l.id))
|
|
187
|
-
return fields.some(f => {
|
|
186
|
+
const configuredIds = new Set(locales.map((l) => l.id))
|
|
187
|
+
return fields.some((f) => {
|
|
188
188
|
if (!isLocaleObj(f)) return false
|
|
189
|
-
return Object.keys(f as object).some(k => !configuredIds.has(k))
|
|
189
|
+
return Object.keys(f as object).some((k) => !configuredIds.has(k))
|
|
190
190
|
})
|
|
191
191
|
}, [currentAsset, locales])
|
|
192
192
|
|
|
@@ -199,9 +199,9 @@ const DialogAssetEdit = (props: Props) => {
|
|
|
199
199
|
if (!locales || locales.length === 0) {
|
|
200
200
|
// Pick the first non-empty value sorted by key for determinism
|
|
201
201
|
const sorted = Object.keys(obj).sort()
|
|
202
|
-
return sorted.map(k => obj[k]).find(v => v) || ''
|
|
202
|
+
return sorted.map((k) => obj[k]).find((v) => v) || ''
|
|
203
203
|
}
|
|
204
|
-
const configuredIds = new Set(locales.map(l => l.id))
|
|
204
|
+
const configuredIds = new Set(locales.map((l) => l.id))
|
|
205
205
|
const cleaned: Record<string, string> = {}
|
|
206
206
|
for (const [key, val] of Object.entries(obj)) {
|
|
207
207
|
if (configuredIds.has(key)) cleaned[key] = val
|
|
@@ -216,15 +216,15 @@ const DialogAssetEdit = (props: Props) => {
|
|
|
216
216
|
altText: cleanField(currentAsset.altText),
|
|
217
217
|
description: cleanField(currentAsset.description),
|
|
218
218
|
...(currentAsset._type === 'sanity.imageAsset' && {
|
|
219
|
-
creditLine: cleanField(currentAsset.creditLine)
|
|
220
|
-
})
|
|
219
|
+
creditLine: cleanField(currentAsset.creditLine),
|
|
220
|
+
}),
|
|
221
221
|
})
|
|
222
222
|
.commit()
|
|
223
223
|
}, [client, currentAsset, locales])
|
|
224
224
|
|
|
225
225
|
// Submit react-hook-form
|
|
226
226
|
const onSubmit: SubmitHandler<AssetFormData> = useCallback(
|
|
227
|
-
formData => {
|
|
227
|
+
(formData) => {
|
|
228
228
|
if (!assetItem?.asset) {
|
|
229
229
|
return
|
|
230
230
|
}
|
|
@@ -245,15 +245,15 @@ const DialogAssetEdit = (props: Props) => {
|
|
|
245
245
|
sanitizedFormData.opt.media.tags?.map((tag: TagSelectOption) => ({
|
|
246
246
|
_ref: tag.value,
|
|
247
247
|
_type: 'reference',
|
|
248
|
-
_weak: true
|
|
249
|
-
})) || null
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
})
|
|
248
|
+
_weak: true,
|
|
249
|
+
})) || null,
|
|
250
|
+
},
|
|
251
|
+
},
|
|
252
|
+
},
|
|
253
|
+
}),
|
|
254
254
|
)
|
|
255
255
|
},
|
|
256
|
-
[assetItem?.asset, dispatch]
|
|
256
|
+
[assetItem?.asset, dispatch],
|
|
257
257
|
)
|
|
258
258
|
|
|
259
259
|
// Listen for asset mutations and update snapshot
|
|
@@ -285,7 +285,7 @@ const DialogAssetEdit = (props: Props) => {
|
|
|
285
285
|
useEffect(() => {
|
|
286
286
|
if (lastRemovedTagIds) {
|
|
287
287
|
const existingTags = (getValues('opt.media.tags') as TagSelectOption[]) || []
|
|
288
|
-
const updatedTags = existingTags.filter(tag => {
|
|
288
|
+
const updatedTags = existingTags.filter((tag) => {
|
|
289
289
|
return !lastRemovedTagIds.includes(tag.value)
|
|
290
290
|
})
|
|
291
291
|
|
|
@@ -359,7 +359,7 @@ const DialogAssetEdit = (props: Props) => {
|
|
|
359
359
|
handleCreateTag,
|
|
360
360
|
currentAsset,
|
|
361
361
|
creditLine,
|
|
362
|
-
locales
|
|
362
|
+
locales,
|
|
363
363
|
}
|
|
364
364
|
|
|
365
365
|
return (
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import {WarningOutlineIcon} from '@sanity/icons'
|
|
2
2
|
import {Box, Button, Flex, Stack, Text} from '@sanity/ui'
|
|
3
|
-
import type {DialogConfirmProps} from '../../types'
|
|
4
3
|
import {type ReactNode} from 'react'
|
|
5
4
|
import {useDispatch} from 'react-redux'
|
|
5
|
+
|
|
6
6
|
import {dialogActions} from '../../modules/dialog'
|
|
7
|
+
import type {DialogConfirmProps} from '../../types'
|
|
7
8
|
import Dialog from '../Dialog'
|
|
8
9
|
|
|
9
10
|
type Props = {
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import {Box} from '@sanity/ui'
|
|
2
|
-
import type {DialogSearchFacetsProps} from '../../types'
|
|
3
2
|
import {type ReactNode, useCallback} from 'react'
|
|
4
3
|
import {useDispatch} from 'react-redux'
|
|
4
|
+
|
|
5
5
|
import {dialogActions} from '../../modules/dialog'
|
|
6
|
+
import type {DialogSearchFacetsProps} from '../../types'
|
|
6
7
|
import Dialog from '../Dialog'
|
|
7
8
|
import SearchFacets from '../SearchFacets'
|
|
8
9
|
import SearchFacetsControl from '../SearchFacetsControl'
|
|
@@ -15,7 +16,7 @@ type Props = {
|
|
|
15
16
|
const DialogSearchFacets = (props: Props) => {
|
|
16
17
|
const {
|
|
17
18
|
children,
|
|
18
|
-
dialog: {id}
|
|
19
|
+
dialog: {id},
|
|
19
20
|
} = props
|
|
20
21
|
|
|
21
22
|
// Redux
|
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import userEvent from '@testing-library/user-event'
|
|
2
1
|
import {screen, waitFor} from '@testing-library/react'
|
|
2
|
+
import userEvent from '@testing-library/user-event'
|
|
3
3
|
import {describe, expect, it, vi} from 'vitest'
|
|
4
|
-
|
|
4
|
+
|
|
5
5
|
import {renderWithProviders} from '../../__tests__/fixtures/renderWithProviders'
|
|
6
6
|
import {createTestRootState} from '../../__tests__/fixtures/rootState'
|
|
7
7
|
import {getDialogRoot, inputByName, withinDialog} from '../../__tests__/fixtures/withinDialog'
|
|
8
8
|
import {tagsActions} from '../../modules/tags'
|
|
9
|
+
import DialogTagCreate from './index'
|
|
9
10
|
|
|
10
11
|
describe('DialogTagCreate', () => {
|
|
11
12
|
it('dispatches tag create flow when form is valid', async () => {
|
|
@@ -13,7 +14,7 @@ describe('DialogTagCreate', () => {
|
|
|
13
14
|
const {store} = renderWithProviders(
|
|
14
15
|
<DialogTagCreate dialog={{id: 'dlg-1', type: 'tagCreate'}}>
|
|
15
16
|
<span />
|
|
16
|
-
</DialogTagCreate
|
|
17
|
+
</DialogTagCreate>,
|
|
17
18
|
)
|
|
18
19
|
|
|
19
20
|
const dlg = withinDialog(/create tag/i, screen)
|
|
@@ -28,7 +29,7 @@ describe('DialogTagCreate', () => {
|
|
|
28
29
|
const {store} = renderWithProviders(
|
|
29
30
|
<DialogTagCreate dialog={{id: 'dlg-1', type: 'tagCreate'}}>
|
|
30
31
|
<span />
|
|
31
|
-
</DialogTagCreate
|
|
32
|
+
</DialogTagCreate>,
|
|
32
33
|
)
|
|
33
34
|
const dispatchSpy = vi.spyOn(store, 'dispatch')
|
|
34
35
|
const dlg = withinDialog(/create tag/i, screen)
|
|
@@ -55,11 +56,11 @@ describe('DialogTagCreate', () => {
|
|
|
55
56
|
renderWithProviders(
|
|
56
57
|
<DialogTagCreate dialog={{id: 'dlg-1', type: 'tagCreate'}}>
|
|
57
58
|
<span />
|
|
58
|
-
</DialogTagCreate
|
|
59
|
+
</DialogTagCreate>,
|
|
59
60
|
)
|
|
60
61
|
|
|
61
62
|
expect(
|
|
62
|
-
withinDialog(/create tag/i, screen).getByRole('button', {name: /save and close/i})
|
|
63
|
+
withinDialog(/create tag/i, screen).getByRole('button', {name: /save and close/i}),
|
|
63
64
|
).toBeDisabled()
|
|
64
65
|
|
|
65
66
|
const nameInput = inputByName(/create tag/i, screen, 'name')
|
|
@@ -67,7 +68,7 @@ describe('DialogTagCreate', () => {
|
|
|
67
68
|
await user.tab()
|
|
68
69
|
await waitFor(() => {
|
|
69
70
|
expect(
|
|
70
|
-
withinDialog(/create tag/i, screen).getByRole('button', {name: /save and close/i})
|
|
71
|
+
withinDialog(/create tag/i, screen).getByRole('button', {name: /save and close/i}),
|
|
71
72
|
).not.toBeDisabled()
|
|
72
73
|
})
|
|
73
74
|
})
|
|
@@ -78,16 +79,16 @@ describe('DialogTagCreate', () => {
|
|
|
78
79
|
dialog: {
|
|
79
80
|
items: [
|
|
80
81
|
{id: 'dlg-1', type: 'tagCreate'},
|
|
81
|
-
{id: 'tags', type: 'tags'}
|
|
82
|
-
]
|
|
83
|
-
}
|
|
82
|
+
{id: 'tags', type: 'tags'},
|
|
83
|
+
],
|
|
84
|
+
},
|
|
84
85
|
})
|
|
85
86
|
|
|
86
87
|
const {store} = renderWithProviders(
|
|
87
88
|
<DialogTagCreate dialog={{id: 'dlg-1', type: 'tagCreate'}}>
|
|
88
89
|
<span />
|
|
89
90
|
</DialogTagCreate>,
|
|
90
|
-
{preloaded: base}
|
|
91
|
+
{preloaded: base},
|
|
91
92
|
)
|
|
92
93
|
|
|
93
94
|
const dlg = withinDialog(/create tag/i, screen)
|
|
@@ -100,20 +101,20 @@ describe('DialogTagCreate', () => {
|
|
|
100
101
|
const base = createTestRootState({
|
|
101
102
|
tags: {
|
|
102
103
|
...createTestRootState().tags,
|
|
103
|
-
creatingError: {message: 'Tag already exists', statusCode: 409}
|
|
104
|
-
}
|
|
104
|
+
creatingError: {message: 'Tag already exists', statusCode: 409},
|
|
105
|
+
},
|
|
105
106
|
})
|
|
106
107
|
|
|
107
108
|
renderWithProviders(
|
|
108
109
|
<DialogTagCreate dialog={{id: 'dlg-1', type: 'tagCreate'}}>
|
|
109
110
|
<span />
|
|
110
111
|
</DialogTagCreate>,
|
|
111
|
-
{preloaded: base}
|
|
112
|
+
{preloaded: base},
|
|
112
113
|
)
|
|
113
114
|
|
|
114
115
|
await waitFor(() => {
|
|
115
116
|
expect(
|
|
116
|
-
getDialogRoot(/create tag/i, screen).querySelector('[data-sanity-icon="error-outline"]')
|
|
117
|
+
getDialogRoot(/create tag/i, screen).querySelector('[data-sanity-icon="error-outline"]'),
|
|
117
118
|
).toBeTruthy()
|
|
118
119
|
})
|
|
119
120
|
})
|
|
@@ -1,14 +1,15 @@
|
|
|
1
|
-
import {zodResolver} from '@hookform/resolvers/zod'
|
|
2
1
|
import {Box, Flex} from '@sanity/ui'
|
|
3
|
-
import type {DialogTagCreateProps, TagFormData} from '../../types'
|
|
4
2
|
import {type ReactNode, useEffect} from 'react'
|
|
5
3
|
import {type SubmitHandler, useForm} from 'react-hook-form'
|
|
6
4
|
import {useDispatch} from 'react-redux'
|
|
5
|
+
|
|
7
6
|
import {tagFormSchema} from '../../formSchema'
|
|
8
7
|
import useTypedSelector from '../../hooks/useTypedSelector'
|
|
9
8
|
import {dialogActions} from '../../modules/dialog'
|
|
10
9
|
import {tagsActions} from '../../modules/tags'
|
|
10
|
+
import type {DialogTagCreateProps, TagFormData} from '../../types'
|
|
11
11
|
import sanitizeFormData from '../../utils/sanitizeFormData'
|
|
12
|
+
import zodFormResolver from '../../utils/zodFormResolver'
|
|
12
13
|
import Dialog from '../Dialog'
|
|
13
14
|
import FormFieldInputText from '../FormFieldInputText'
|
|
14
15
|
import FormSubmitButton from '../FormSubmitButton'
|
|
@@ -21,26 +22,26 @@ type Props = {
|
|
|
21
22
|
const DialogTagCreate = (props: Props) => {
|
|
22
23
|
const {
|
|
23
24
|
children,
|
|
24
|
-
dialog: {id}
|
|
25
|
+
dialog: {id},
|
|
25
26
|
} = props
|
|
26
27
|
|
|
27
28
|
const dispatch = useDispatch()
|
|
28
29
|
|
|
29
|
-
const creating = useTypedSelector(state => state.tags.creating)
|
|
30
|
-
const creatingError = useTypedSelector(state => state.tags.creatingError)
|
|
30
|
+
const creating = useTypedSelector((state) => state.tags.creating)
|
|
31
|
+
const creatingError = useTypedSelector((state) => state.tags.creatingError)
|
|
31
32
|
|
|
32
33
|
const {
|
|
33
34
|
// Read the formState before render to subscribe the form state through Proxy
|
|
34
35
|
formState: {errors, isDirty, isValid},
|
|
35
36
|
handleSubmit,
|
|
36
37
|
register,
|
|
37
|
-
setError
|
|
38
|
+
setError,
|
|
38
39
|
} = useForm<TagFormData>({
|
|
39
40
|
defaultValues: {
|
|
40
|
-
name: ''
|
|
41
|
+
name: '',
|
|
41
42
|
},
|
|
42
43
|
mode: 'onChange',
|
|
43
|
-
resolver:
|
|
44
|
+
resolver: zodFormResolver<TagFormData>(tagFormSchema),
|
|
44
45
|
})
|
|
45
46
|
|
|
46
47
|
const formUpdating = creating
|
|
@@ -50,7 +51,7 @@ const DialogTagCreate = (props: Props) => {
|
|
|
50
51
|
}
|
|
51
52
|
|
|
52
53
|
// - submit react-hook-form
|
|
53
|
-
const onSubmit: SubmitHandler<TagFormData> = formData => {
|
|
54
|
+
const onSubmit: SubmitHandler<TagFormData> = (formData) => {
|
|
54
55
|
const sanitizedFormData = sanitizeFormData(formData)
|
|
55
56
|
|
|
56
57
|
dispatch(tagsActions.createRequest({name: sanitizedFormData.name}))
|
|
@@ -59,7 +60,7 @@ const DialogTagCreate = (props: Props) => {
|
|
|
59
60
|
useEffect(() => {
|
|
60
61
|
if (creatingError) {
|
|
61
62
|
setError('name', {
|
|
62
|
-
message: creatingError?.message
|
|
63
|
+
message: creatingError?.message,
|
|
63
64
|
})
|
|
64
65
|
}
|
|
65
66
|
}, [creatingError, setError])
|