sanity-plugin-media 4.3.1 → 4.3.3
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.mjs → index.cjs} +1115 -1242
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +462 -0
- package/dist/index.d.ts +263 -195
- package/dist/index.js +1125 -1237
- package/dist/index.js.map +1 -1
- package/package.json +45 -68
- 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
- package/dist/index.d.mts +0 -394
- package/dist/index.mjs.map +0 -1
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import {createSelector, createSlice, type PayloadAction} from '@reduxjs/toolkit'
|
|
2
2
|
import type {ClientError, SanityAssetDocument, SanityImageAssetDocument} from '@sanity/client'
|
|
3
|
-
import type {HttpError, MyEpic, SanityUploadProgressEvent, UploadItem} from '../../types'
|
|
4
3
|
import groq from 'groq'
|
|
5
4
|
import type {Selector} from 'react-redux'
|
|
6
5
|
import {empty, merge, of} from 'rxjs'
|
|
7
6
|
import {catchError, delay, filter, mergeMap, takeUntil, withLatestFrom} from 'rxjs/operators'
|
|
7
|
+
|
|
8
|
+
import type {HttpError, MyEpic, SanityUploadProgressEvent, UploadItem} from '../../types'
|
|
8
9
|
import constructFilter from '../../utils/constructFilter'
|
|
9
10
|
import {generatePreviewBlobUrl$} from '../../utils/generatePreviewBlobUrl'
|
|
10
11
|
import {hashFile$, uploadAsset$} from '../../utils/uploadSanityAsset'
|
|
@@ -19,13 +20,13 @@ export type UploadsReducerState = {
|
|
|
19
20
|
|
|
20
21
|
const initialState = {
|
|
21
22
|
allIds: [],
|
|
22
|
-
byIds: {}
|
|
23
|
+
byIds: {},
|
|
23
24
|
} as UploadsReducerState
|
|
24
25
|
|
|
25
26
|
const uploadsSlice = createSlice({
|
|
26
27
|
name: 'uploads',
|
|
27
28
|
initialState,
|
|
28
|
-
extraReducers: builder => {
|
|
29
|
+
extraReducers: (builder) => {
|
|
29
30
|
builder //
|
|
30
31
|
.addCase(UPLOADS_ACTIONS.uploadComplete, (state, action) => {
|
|
31
32
|
const {asset} = action.payload
|
|
@@ -37,7 +38,7 @@ const uploadsSlice = createSlice({
|
|
|
37
38
|
reducers: {
|
|
38
39
|
checkRequest(
|
|
39
40
|
_state,
|
|
40
|
-
_action: PayloadAction<{assets: (SanityAssetDocument | SanityImageAssetDocument)[]}
|
|
41
|
+
_action: PayloadAction<{assets: (SanityAssetDocument | SanityImageAssetDocument)[]}>,
|
|
41
42
|
) {
|
|
42
43
|
//
|
|
43
44
|
},
|
|
@@ -46,7 +47,7 @@ const uploadsSlice = createSlice({
|
|
|
46
47
|
|
|
47
48
|
const assetHashes = Object.keys(results)
|
|
48
49
|
|
|
49
|
-
assetHashes.forEach(hash => {
|
|
50
|
+
assetHashes.forEach((hash) => {
|
|
50
51
|
const deleteIndex = state.allIds.indexOf(hash)
|
|
51
52
|
if (deleteIndex >= 0) {
|
|
52
53
|
state.allIds.splice(deleteIndex, 1)
|
|
@@ -88,13 +89,13 @@ const uploadsSlice = createSlice({
|
|
|
88
89
|
},
|
|
89
90
|
uploadRequest(
|
|
90
91
|
_state,
|
|
91
|
-
_action: PayloadAction<{file: File; forceAsAssetType?: 'file' | 'image'}
|
|
92
|
+
_action: PayloadAction<{file: File; forceAsAssetType?: 'file' | 'image'}>,
|
|
92
93
|
) {
|
|
93
94
|
//
|
|
94
95
|
},
|
|
95
96
|
uploadProgress(
|
|
96
97
|
state,
|
|
97
|
-
action: PayloadAction<{event: SanityUploadProgressEvent; uploadHash: string}
|
|
98
|
+
action: PayloadAction<{event: SanityUploadProgressEvent; uploadHash: string}>,
|
|
98
99
|
) {
|
|
99
100
|
const {event, uploadHash} = action.payload
|
|
100
101
|
state.byIds[uploadHash].percent = event.percent
|
|
@@ -106,8 +107,8 @@ const uploadsSlice = createSlice({
|
|
|
106
107
|
state.allIds.push(uploadItem.hash)
|
|
107
108
|
}
|
|
108
109
|
state.byIds[uploadItem.hash] = uploadItem
|
|
109
|
-
}
|
|
110
|
-
}
|
|
110
|
+
},
|
|
111
|
+
},
|
|
111
112
|
})
|
|
112
113
|
|
|
113
114
|
// Epics
|
|
@@ -115,21 +116,21 @@ const uploadsSlice = createSlice({
|
|
|
115
116
|
export const uploadsAssetStartEpic: MyEpic = (action$, _state$, {client}) =>
|
|
116
117
|
action$.pipe(
|
|
117
118
|
filter(uploadsActions.uploadStart.match),
|
|
118
|
-
mergeMap(action => {
|
|
119
|
+
mergeMap((action) => {
|
|
119
120
|
const {file, uploadItem} = action.payload
|
|
120
121
|
|
|
121
122
|
return merge(
|
|
122
123
|
// Generate low res preview
|
|
123
124
|
of(null).pipe(
|
|
124
125
|
mergeMap(() => generatePreviewBlobUrl$(file)),
|
|
125
|
-
mergeMap(url => {
|
|
126
|
+
mergeMap((url) => {
|
|
126
127
|
return of(
|
|
127
128
|
uploadsActions.previewReady({
|
|
128
129
|
blobUrl: url,
|
|
129
|
-
hash: uploadItem.hash
|
|
130
|
-
})
|
|
130
|
+
hash: uploadItem.hash,
|
|
131
|
+
}),
|
|
131
132
|
)
|
|
132
|
-
})
|
|
133
|
+
}),
|
|
133
134
|
),
|
|
134
135
|
// Upload asset and receive progress / complete events
|
|
135
136
|
of(null).pipe(
|
|
@@ -138,23 +139,23 @@ export const uploadsAssetStartEpic: MyEpic = (action$, _state$, {client}) =>
|
|
|
138
139
|
takeUntil(
|
|
139
140
|
action$.pipe(
|
|
140
141
|
filter(uploadsActions.uploadCancel.match),
|
|
141
|
-
filter(v => v.payload.hash === uploadItem.hash)
|
|
142
|
-
)
|
|
142
|
+
filter((v) => v.payload.hash === uploadItem.hash),
|
|
143
|
+
),
|
|
143
144
|
),
|
|
144
|
-
mergeMap(event => {
|
|
145
|
+
mergeMap((event) => {
|
|
145
146
|
if (event?.type === 'complete') {
|
|
146
147
|
return of(
|
|
147
148
|
UPLOADS_ACTIONS.uploadComplete({
|
|
148
|
-
asset: event.asset
|
|
149
|
-
})
|
|
149
|
+
asset: event.asset,
|
|
150
|
+
}),
|
|
150
151
|
)
|
|
151
152
|
}
|
|
152
153
|
if (event?.type === 'progress' && event?.stage === 'upload') {
|
|
153
154
|
return of(
|
|
154
155
|
uploadsActions.uploadProgress({
|
|
155
156
|
event,
|
|
156
|
-
uploadHash: uploadItem.hash
|
|
157
|
-
})
|
|
157
|
+
uploadHash: uploadItem.hash,
|
|
158
|
+
}),
|
|
158
159
|
)
|
|
159
160
|
}
|
|
160
161
|
return empty()
|
|
@@ -164,15 +165,15 @@ export const uploadsAssetStartEpic: MyEpic = (action$, _state$, {client}) =>
|
|
|
164
165
|
uploadsActions.uploadError({
|
|
165
166
|
error: {
|
|
166
167
|
message: error?.message || 'Internal error',
|
|
167
|
-
statusCode: error?.statusCode || 500
|
|
168
|
+
statusCode: error?.statusCode || 500,
|
|
168
169
|
},
|
|
169
|
-
hash: uploadItem.hash
|
|
170
|
-
})
|
|
171
|
-
)
|
|
172
|
-
)
|
|
173
|
-
)
|
|
170
|
+
hash: uploadItem.hash,
|
|
171
|
+
}),
|
|
172
|
+
),
|
|
173
|
+
),
|
|
174
|
+
),
|
|
174
175
|
)
|
|
175
|
-
})
|
|
176
|
+
}),
|
|
176
177
|
)
|
|
177
178
|
|
|
178
179
|
export const uploadsAssetUploadEpic: MyEpic = (action$, state$) =>
|
|
@@ -187,12 +188,12 @@ export const uploadsAssetUploadEpic: MyEpic = (action$, state$) =>
|
|
|
187
188
|
// This will throw in insecure contexts (non-localhost / https)
|
|
188
189
|
mergeMap(() => hashFile$(file)),
|
|
189
190
|
// Ignore if the file exists and is currently being uploaded
|
|
190
|
-
filter(hash => {
|
|
191
|
+
filter((hash) => {
|
|
191
192
|
const exists = !!state.uploads.byIds[hash]
|
|
192
193
|
return !exists
|
|
193
194
|
}),
|
|
194
195
|
// Dispatch start action and begin upload process
|
|
195
|
-
mergeMap(hash => {
|
|
196
|
+
mergeMap((hash) => {
|
|
196
197
|
const assetType = forceAsAssetType || (file.type.indexOf('image') >= 0 ? 'image' : 'file')
|
|
197
198
|
const uploadItem = {
|
|
198
199
|
_type: 'upload',
|
|
@@ -200,24 +201,24 @@ export const uploadsAssetUploadEpic: MyEpic = (action$, state$) =>
|
|
|
200
201
|
hash,
|
|
201
202
|
name: file.name,
|
|
202
203
|
size: file.size,
|
|
203
|
-
status: 'queued'
|
|
204
|
+
status: 'queued',
|
|
204
205
|
} as UploadItem
|
|
205
206
|
return of(uploadsActions.uploadStart({file, uploadItem}))
|
|
206
|
-
})
|
|
207
|
+
}),
|
|
207
208
|
)
|
|
208
|
-
})
|
|
209
|
+
}),
|
|
209
210
|
)
|
|
210
211
|
|
|
211
|
-
export const uploadsCompleteQueueEpic: MyEpic = action$ =>
|
|
212
|
+
export const uploadsCompleteQueueEpic: MyEpic = (action$) =>
|
|
212
213
|
action$.pipe(
|
|
213
214
|
filter(UPLOADS_ACTIONS.uploadComplete.match),
|
|
214
|
-
mergeMap(action => {
|
|
215
|
+
mergeMap((action) => {
|
|
215
216
|
return of(
|
|
216
217
|
uploadsActions.checkRequest({
|
|
217
|
-
assets: [action.payload.asset]
|
|
218
|
-
})
|
|
218
|
+
assets: [action.payload.asset],
|
|
219
|
+
}),
|
|
219
220
|
)
|
|
220
|
-
})
|
|
221
|
+
}),
|
|
221
222
|
)
|
|
222
223
|
|
|
223
224
|
export const uploadsCheckRequestEpic: MyEpic = (action$, state$, {client}) =>
|
|
@@ -227,12 +228,12 @@ export const uploadsCheckRequestEpic: MyEpic = (action$, state$, {client}) =>
|
|
|
227
228
|
mergeMap(([action, state]) => {
|
|
228
229
|
const {assets} = action.payload
|
|
229
230
|
|
|
230
|
-
const documentIds = assets.map(asset => asset._id)
|
|
231
|
+
const documentIds = assets.map((asset) => asset._id)
|
|
231
232
|
|
|
232
233
|
const constructedFilter = constructFilter({
|
|
233
234
|
assetTypes: state.assets.assetTypes,
|
|
234
235
|
searchFacets: state.search.facets,
|
|
235
|
-
searchQuery: state.search.query
|
|
236
|
+
searchQuery: state.search.query,
|
|
236
237
|
})
|
|
237
238
|
|
|
238
239
|
const query = groq`
|
|
@@ -242,7 +243,7 @@ export const uploadsCheckRequestEpic: MyEpic = (action$, state$, {client}) =>
|
|
|
242
243
|
return of(action).pipe(
|
|
243
244
|
delay(1000), // give Sanity some time to register the recently uploaded asset
|
|
244
245
|
mergeMap(() => client.observable.fetch<string[]>(query, {documentIds})),
|
|
245
|
-
mergeMap(resultHashes => {
|
|
246
|
+
mergeMap((resultHashes) => {
|
|
246
247
|
const checkedResults = assets.reduce((acc: Record<string, string | null>, asset) => {
|
|
247
248
|
acc[asset.sha1hash] = resultHashes.includes(asset.sha1hash) ? asset._id : null
|
|
248
249
|
return acc
|
|
@@ -250,11 +251,11 @@ export const uploadsCheckRequestEpic: MyEpic = (action$, state$, {client}) =>
|
|
|
250
251
|
|
|
251
252
|
return of(
|
|
252
253
|
uploadsActions.checkComplete({results: checkedResults}), //
|
|
253
|
-
assetsActions.insertUploads({results: checkedResults})
|
|
254
|
+
assetsActions.insertUploads({results: checkedResults}),
|
|
254
255
|
)
|
|
255
|
-
})
|
|
256
|
+
}),
|
|
256
257
|
)
|
|
257
|
-
})
|
|
258
|
+
}),
|
|
258
259
|
)
|
|
259
260
|
|
|
260
261
|
// Selectors
|
|
@@ -266,14 +267,14 @@ const selectUploadsAllIds = (state: RootReducerState) => state.uploads.allIds
|
|
|
266
267
|
export const selectUploadById = createSelector(
|
|
267
268
|
[
|
|
268
269
|
(state: RootReducerState) => state.uploads.byIds,
|
|
269
|
-
(_state: RootReducerState, uploadId: string) => uploadId
|
|
270
|
+
(_state: RootReducerState, uploadId: string) => uploadId,
|
|
270
271
|
],
|
|
271
|
-
(byIds, uploadId) => byIds[uploadId]
|
|
272
|
+
(byIds, uploadId) => byIds[uploadId],
|
|
272
273
|
)
|
|
273
274
|
|
|
274
275
|
export const selectUploads: Selector<RootReducerState, UploadItem[]> = createSelector(
|
|
275
276
|
[selectUploadsByIds, selectUploadsAllIds],
|
|
276
|
-
(byIds, allIds) => allIds.map(id => byIds[id])
|
|
277
|
+
(byIds, allIds) => allIds.map((id) => byIds[id]),
|
|
277
278
|
)
|
|
278
279
|
|
|
279
280
|
export const uploadsActions = {...uploadsSlice.actions}
|
|
@@ -1,28 +1,29 @@
|
|
|
1
1
|
// @vitest-environment node
|
|
2
2
|
|
|
3
|
-
import {describe, expect, it, vi} from 'vitest'
|
|
4
|
-
import {firstValueFrom, of} from 'rxjs'
|
|
5
3
|
import type {SanityClient} from '@sanity/client'
|
|
4
|
+
import {firstValueFrom, of} from 'rxjs'
|
|
5
|
+
import {describe, expect, it, vi} from 'vitest'
|
|
6
|
+
|
|
6
7
|
import checkTagName from './checkTagName'
|
|
7
8
|
|
|
8
9
|
describe('checkTagName', () => {
|
|
9
10
|
it('errors with 409 when a tag with the same slug exists', async () => {
|
|
10
11
|
const client = {
|
|
11
|
-
fetch: vi.fn().mockResolvedValue(1)
|
|
12
|
+
fetch: vi.fn().mockResolvedValue(1),
|
|
12
13
|
} as unknown as SanityClient
|
|
13
14
|
|
|
14
15
|
await expect(
|
|
15
|
-
firstValueFrom(of(null).pipe(checkTagName(client, 'existing')))
|
|
16
|
+
firstValueFrom(of(null).pipe(checkTagName(client, 'existing'))),
|
|
16
17
|
).rejects.toMatchObject({statusCode: 409, message: 'Tag already exists'})
|
|
17
18
|
})
|
|
18
19
|
|
|
19
20
|
it('emits true when name is available', async () => {
|
|
20
21
|
const client = {
|
|
21
|
-
fetch: vi.fn().mockResolvedValue(0)
|
|
22
|
+
fetch: vi.fn().mockResolvedValue(0),
|
|
22
23
|
} as unknown as SanityClient
|
|
23
24
|
|
|
24
25
|
await expect(firstValueFrom(of(null).pipe(checkTagName(client, 'fresh-name')))).resolves.toBe(
|
|
25
|
-
true
|
|
26
|
+
true,
|
|
26
27
|
)
|
|
27
28
|
})
|
|
28
29
|
})
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import type {SanityClient} from '@sanity/client'
|
|
2
|
-
import type {HttpError} from '../types'
|
|
3
2
|
import groq from 'groq'
|
|
4
3
|
import {from, Observable, of, throwError} from 'rxjs'
|
|
5
4
|
import {mergeMap} from 'rxjs/operators'
|
|
5
|
+
|
|
6
6
|
import {TAG_DOCUMENT_NAME} from '../constants'
|
|
7
|
+
import type {HttpError} from '../types'
|
|
7
8
|
|
|
8
9
|
const checkTagName = (client: SanityClient, name: string) => {
|
|
9
10
|
return function <T>(source: Observable<T>): Observable<boolean> {
|
|
@@ -11,20 +12,20 @@ const checkTagName = (client: SanityClient, name: string) => {
|
|
|
11
12
|
mergeMap(() => {
|
|
12
13
|
return from(
|
|
13
14
|
client.fetch(groq`count(*[_type == "${TAG_DOCUMENT_NAME}" && name.current == $name])`, {
|
|
14
|
-
name
|
|
15
|
-
})
|
|
15
|
+
name,
|
|
16
|
+
}),
|
|
16
17
|
) as Observable<number>
|
|
17
18
|
}),
|
|
18
19
|
mergeMap((existingTagCount: number) => {
|
|
19
20
|
if (existingTagCount > 0) {
|
|
20
21
|
return throwError({
|
|
21
22
|
message: 'Tag already exists',
|
|
22
|
-
statusCode: 409
|
|
23
|
+
statusCode: 409,
|
|
23
24
|
} as HttpError)
|
|
24
25
|
}
|
|
25
26
|
|
|
26
27
|
return of(true)
|
|
27
|
-
})
|
|
28
|
+
}),
|
|
28
29
|
)
|
|
29
30
|
}
|
|
30
31
|
}
|
|
@@ -7,17 +7,17 @@ const debugThrottle = (throttled?: boolean) => {
|
|
|
7
7
|
() => !!throttled,
|
|
8
8
|
source.pipe(
|
|
9
9
|
delay(3000),
|
|
10
|
-
mergeMap(v => {
|
|
10
|
+
mergeMap((v) => {
|
|
11
11
|
if (Math.random() > 0.5) {
|
|
12
12
|
return throwError({
|
|
13
13
|
message: 'Test error',
|
|
14
|
-
statusCode: 500
|
|
14
|
+
statusCode: 500,
|
|
15
15
|
})
|
|
16
16
|
}
|
|
17
17
|
return of(v)
|
|
18
|
-
})
|
|
18
|
+
}),
|
|
19
19
|
),
|
|
20
|
-
source
|
|
20
|
+
source,
|
|
21
21
|
)
|
|
22
22
|
}
|
|
23
23
|
}
|
package/src/plugin.tsx
CHANGED
|
@@ -1,54 +1,54 @@
|
|
|
1
|
-
import {type AssetSource, type Tool as SanityTool, definePlugin} from 'sanity'
|
|
2
1
|
import {ImageIcon} from '@sanity/icons'
|
|
2
|
+
import {type AssetSource, type Tool as SanityTool, definePlugin} from 'sanity'
|
|
3
|
+
|
|
3
4
|
import FormBuilderTool from './components/FormBuilderTool'
|
|
4
5
|
import Tool from './components/Tool'
|
|
6
|
+
import {ToolOptionsProvider} from './contexts/ToolOptionsContext'
|
|
5
7
|
import mediaTag from './schemas/tag'
|
|
6
8
|
import type {MediaToolOptions} from './types'
|
|
7
|
-
import {ToolOptionsProvider} from './contexts/ToolOptionsContext'
|
|
8
9
|
|
|
9
10
|
const plugin = {
|
|
10
11
|
icon: ImageIcon,
|
|
11
12
|
name: 'media',
|
|
12
|
-
title: 'Media'
|
|
13
|
+
title: 'Media',
|
|
13
14
|
}
|
|
14
15
|
|
|
15
16
|
export const mediaAssetSource = {
|
|
16
17
|
...plugin,
|
|
17
|
-
component: FormBuilderTool
|
|
18
|
+
component: FormBuilderTool,
|
|
18
19
|
} satisfies AssetSource
|
|
19
20
|
|
|
20
21
|
const tool = {
|
|
21
22
|
...plugin,
|
|
22
23
|
component: Tool,
|
|
23
|
-
|
|
24
|
-
__internalApplicationType: 'sanity/media'
|
|
24
|
+
__internalApplicationType: 'sanity/media',
|
|
25
25
|
} satisfies SanityTool
|
|
26
26
|
|
|
27
|
-
export const media = definePlugin<MediaToolOptions | void>(options => ({
|
|
27
|
+
export const media = definePlugin<MediaToolOptions | void>((options) => ({
|
|
28
28
|
name: 'media',
|
|
29
29
|
studio: {
|
|
30
30
|
components: {
|
|
31
|
-
layout: props => (
|
|
31
|
+
layout: (props) => (
|
|
32
32
|
<ToolOptionsProvider options={options}>{props.renderDefault(props)}</ToolOptionsProvider>
|
|
33
|
-
)
|
|
34
|
-
}
|
|
33
|
+
),
|
|
34
|
+
},
|
|
35
35
|
},
|
|
36
36
|
form: {
|
|
37
37
|
file: {
|
|
38
|
-
assetSources: prev => {
|
|
38
|
+
assetSources: (prev) => {
|
|
39
39
|
return [...prev, mediaAssetSource]
|
|
40
|
-
}
|
|
40
|
+
},
|
|
41
41
|
},
|
|
42
42
|
image: {
|
|
43
|
-
assetSources: prev => {
|
|
43
|
+
assetSources: (prev) => {
|
|
44
44
|
return [...prev, mediaAssetSource]
|
|
45
|
-
}
|
|
46
|
-
}
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
47
|
},
|
|
48
48
|
schema: {
|
|
49
|
-
types: [mediaTag]
|
|
49
|
+
types: [mediaTag],
|
|
50
50
|
},
|
|
51
|
-
tools: prev => {
|
|
51
|
+
tools: (prev) => {
|
|
52
52
|
return [...prev, tool]
|
|
53
|
-
}
|
|
53
|
+
},
|
|
54
54
|
}))
|
package/src/schemas/tag.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {TAG_DOCUMENT_NAME} from '../constants'
|
|
2
1
|
import TagIcon from '../components/TagIcon'
|
|
2
|
+
import {TAG_DOCUMENT_NAME} from '../constants'
|
|
3
3
|
|
|
4
4
|
export default {
|
|
5
5
|
title: 'Media Tag',
|
|
@@ -10,19 +10,19 @@ export default {
|
|
|
10
10
|
{
|
|
11
11
|
title: 'Name',
|
|
12
12
|
name: 'name',
|
|
13
|
-
type: 'slug'
|
|
14
|
-
}
|
|
13
|
+
type: 'slug',
|
|
14
|
+
},
|
|
15
15
|
],
|
|
16
16
|
preview: {
|
|
17
17
|
select: {
|
|
18
|
-
name: 'name'
|
|
18
|
+
name: 'name',
|
|
19
19
|
},
|
|
20
20
|
prepare(selection: any) {
|
|
21
21
|
const {name} = selection
|
|
22
22
|
return {
|
|
23
23
|
media: TagIcon,
|
|
24
|
-
title: name?.current
|
|
24
|
+
title: name?.current,
|
|
25
25
|
}
|
|
26
|
-
}
|
|
27
|
-
}
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
28
|
}
|
|
@@ -2,6 +2,7 @@ import {AddIcon, ChevronDownIcon, CloseIcon} from '@sanity/icons'
|
|
|
2
2
|
import {Box, Card, Flex, rem, studioTheme, Text, type ThemeColorSchemeKey} from '@sanity/ui'
|
|
3
3
|
import {components, type StylesConfig} from 'react-select'
|
|
4
4
|
import {Virtuoso} from 'react-virtuoso'
|
|
5
|
+
|
|
5
6
|
import {getSchemeColor} from '../../utils/getSchemeColor'
|
|
6
7
|
|
|
7
8
|
const {radius: themeRadius, space: themeSpace} = studioTheme
|
|
@@ -18,79 +19,79 @@ export const reactSelectStyles = (scheme: ThemeColorSchemeKey): StylesConfig =>
|
|
|
18
19
|
|
|
19
20
|
return {
|
|
20
21
|
...styles,
|
|
21
|
-
backgroundColor: 'var(--card-bg-color)',
|
|
22
|
-
color: 'inherit',
|
|
23
|
-
border: 'none',
|
|
24
|
-
borderRadius: themeRadius[1],
|
|
22
|
+
'backgroundColor': 'var(--card-bg-color)',
|
|
23
|
+
'color': 'inherit',
|
|
24
|
+
'border': 'none',
|
|
25
|
+
'borderRadius': themeRadius[1],
|
|
25
26
|
boxShadow,
|
|
26
|
-
margin: 0,
|
|
27
|
-
minHeight: '35px',
|
|
28
|
-
outline: 'none',
|
|
29
|
-
padding: rem(themeSpace[1]),
|
|
30
|
-
transition: 'none',
|
|
27
|
+
'margin': 0,
|
|
28
|
+
'minHeight': '35px',
|
|
29
|
+
'outline': 'none',
|
|
30
|
+
'padding': rem(themeSpace[1]),
|
|
31
|
+
'transition': 'none',
|
|
31
32
|
'&:hover': {
|
|
32
|
-
boxShadow: `inset 0 0 0 1px ${getSchemeColor(scheme, 'inputHoveredBorder')}
|
|
33
|
-
}
|
|
33
|
+
boxShadow: `inset 0 0 0 1px ${getSchemeColor(scheme, 'inputHoveredBorder')}`,
|
|
34
|
+
},
|
|
34
35
|
}
|
|
35
36
|
},
|
|
36
37
|
indicatorsContainer: (styles, {isDisabled}) => ({
|
|
37
38
|
...styles,
|
|
38
|
-
opacity: isDisabled ? 0.25 : 1
|
|
39
|
+
opacity: isDisabled ? 0.25 : 1,
|
|
39
40
|
}),
|
|
40
|
-
input: styles => ({
|
|
41
|
+
input: (styles) => ({
|
|
41
42
|
...styles,
|
|
42
43
|
color: 'var(--card-fg-color)',
|
|
43
44
|
fontFamily: studioTheme.fonts.text.family,
|
|
44
|
-
marginLeft: rem(themeSpace[2])
|
|
45
|
+
marginLeft: rem(themeSpace[2]),
|
|
45
46
|
}),
|
|
46
|
-
menuList: styles => ({
|
|
47
|
-
...styles
|
|
47
|
+
menuList: (styles) => ({
|
|
48
|
+
...styles,
|
|
48
49
|
}),
|
|
49
50
|
multiValue: (styles, {isDisabled}) => ({
|
|
50
51
|
...styles,
|
|
51
52
|
backgroundColor: getSchemeColor(scheme, 'mutedHoveredBg'),
|
|
52
53
|
borderRadius: themeRadius[2],
|
|
53
|
-
opacity: isDisabled ? 0.5 : 1
|
|
54
|
+
opacity: isDisabled ? 0.5 : 1,
|
|
54
55
|
}),
|
|
55
56
|
multiValueLabel: () => ({
|
|
56
57
|
color: getSchemeColor(scheme, 'mutedHoveredFg'),
|
|
57
58
|
fontSize: 'inherit',
|
|
58
|
-
padding: 0
|
|
59
|
+
padding: 0,
|
|
59
60
|
}),
|
|
60
|
-
multiValueRemove: styles => ({
|
|
61
|
+
multiValueRemove: (styles) => ({
|
|
61
62
|
...styles,
|
|
62
|
-
borderTopLeftRadius: 0,
|
|
63
|
-
borderBottomLeftRadius: 0,
|
|
64
|
-
svg: {color: getSchemeColor(scheme, 'mutedHoveredFg')},
|
|
63
|
+
'borderTopLeftRadius': 0,
|
|
64
|
+
'borderBottomLeftRadius': 0,
|
|
65
|
+
'svg': {color: getSchemeColor(scheme, 'mutedHoveredFg')},
|
|
65
66
|
'&:hover': {
|
|
66
|
-
backgroundColor: getSchemeColor(scheme, 'mutedSelectedBg')
|
|
67
|
-
}
|
|
67
|
+
backgroundColor: getSchemeColor(scheme, 'mutedSelectedBg'),
|
|
68
|
+
},
|
|
68
69
|
}),
|
|
69
|
-
noOptionsMessage: styles => ({
|
|
70
|
+
noOptionsMessage: (styles) => ({
|
|
70
71
|
...styles,
|
|
71
72
|
fontFamily: studioTheme.fonts.text.family,
|
|
72
|
-
lineHeight: '1em'
|
|
73
|
+
lineHeight: '1em',
|
|
73
74
|
}),
|
|
74
75
|
option: (styles, {isFocused}) => ({
|
|
75
76
|
...styles,
|
|
76
|
-
backgroundColor: isFocused ? getSchemeColor(scheme, 'spotBlue') : 'transparent',
|
|
77
|
-
borderRadius: themeRadius[2],
|
|
78
|
-
color: isFocused ? getSchemeColor(scheme, 'bg') : 'inherit',
|
|
79
|
-
padding: `${rem(themeSpace[1])} ${rem(themeSpace[2])}`,
|
|
77
|
+
'backgroundColor': isFocused ? getSchemeColor(scheme, 'spotBlue') : 'transparent',
|
|
78
|
+
'borderRadius': themeRadius[2],
|
|
79
|
+
'color': isFocused ? getSchemeColor(scheme, 'bg') : 'inherit',
|
|
80
|
+
'padding': `${rem(themeSpace[1])} ${rem(themeSpace[2])}`,
|
|
80
81
|
'&:hover': {
|
|
81
82
|
backgroundColor: getSchemeColor(scheme, 'spotBlue'),
|
|
82
|
-
color: getSchemeColor(scheme, 'bg')
|
|
83
|
-
}
|
|
83
|
+
color: getSchemeColor(scheme, 'bg'),
|
|
84
|
+
},
|
|
84
85
|
}),
|
|
85
|
-
placeholder: styles => ({
|
|
86
|
+
placeholder: (styles) => ({
|
|
86
87
|
...styles,
|
|
87
|
-
marginLeft: rem(themeSpace[2])
|
|
88
|
+
marginLeft: rem(themeSpace[2]),
|
|
88
89
|
}),
|
|
89
|
-
valueContainer: styles => ({
|
|
90
|
+
valueContainer: (styles) => ({
|
|
90
91
|
...styles,
|
|
91
92
|
margin: 0,
|
|
92
|
-
padding: 0
|
|
93
|
-
})
|
|
93
|
+
padding: 0,
|
|
94
|
+
}),
|
|
94
95
|
}
|
|
95
96
|
}
|
|
96
97
|
|
|
@@ -129,7 +130,7 @@ const MenuList = (props: any) => {
|
|
|
129
130
|
return (
|
|
130
131
|
<Virtuoso
|
|
131
132
|
className="media__custom-scrollbar"
|
|
132
|
-
itemContent={index => {
|
|
133
|
+
itemContent={(index) => {
|
|
133
134
|
const item = children[index]
|
|
134
135
|
return <Option {...item.props} />
|
|
135
136
|
}}
|
|
@@ -179,5 +180,5 @@ export const reactSelectComponents = {
|
|
|
179
180
|
MenuList,
|
|
180
181
|
MultiValueLabel,
|
|
181
182
|
MultiValueRemove,
|
|
182
|
-
Option
|
|
183
|
+
Option,
|
|
183
184
|
}
|