sanity-plugin-media 4.3.6 → 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/package.json +6 -15
- package/dist/index.cjs +0 -4721
- package/dist/index.cjs.map +0 -1
- package/dist/index.d.cts +0 -239
- package/dist/index.d.cts.map +0 -1
- 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 -85
- 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 -493
- package/src/components/DialogConfirm/index.tsx +0 -90
- package/src/components/DialogSearchFacets/index.tsx +0 -42
- package/src/components/DialogTagCreate/DialogTagCreate.test.tsx +0 -121
- package/src/components/DialogTagCreate/index.tsx +0 -111
- package/src/components/DialogTagEdit/DialogTagEdit.test.tsx +0 -165
- package/src/components/DialogTagEdit/index.tsx +0 -201
- 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 -419
- 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 -38
- package/src/contexts/DropzoneDispatchContext.tsx +0 -32
- package/src/contexts/ToolOptionsContext.tsx +0 -66
- package/src/formSchema/index.test.ts +0 -56
- package/src/formSchema/index.ts +0 -39
- package/src/hooks/useBreakpointIndex.ts +0 -50
- package/src/hooks/useKeyPress.ts +0 -39
- 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 -782
- 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 -117
- 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 -272
- 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 -346
- package/src/types/sanity-ui.d.ts +0 -5
- 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 -68
- 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,44 +0,0 @@
|
|
|
1
|
-
import {describe, expect, it} from 'vitest'
|
|
2
|
-
|
|
3
|
-
import type {Tag, TagItem} from '../types'
|
|
4
|
-
import getTagSelectOptions from './getTagSelectOptions'
|
|
5
|
-
|
|
6
|
-
function tagItem(partial: Partial<TagItem> & Pick<TagItem, 'tag'>): TagItem {
|
|
7
|
-
return {
|
|
8
|
-
_type: 'tag',
|
|
9
|
-
picked: false,
|
|
10
|
-
updating: false,
|
|
11
|
-
...partial,
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
const makeTag = (id: string, name: string): Tag => ({
|
|
16
|
-
_id: id,
|
|
17
|
-
_type: 'media.tag',
|
|
18
|
-
_createdAt: '',
|
|
19
|
-
_updatedAt: '',
|
|
20
|
-
_rev: 'r1',
|
|
21
|
-
name: {_type: 'slug', current: name},
|
|
22
|
-
})
|
|
23
|
-
|
|
24
|
-
describe('getTagSelectOptions', () => {
|
|
25
|
-
it('maps tag items to label/value options', () => {
|
|
26
|
-
const tags = [tagItem({tag: makeTag('t1', 'alpha')}), tagItem({tag: makeTag('t2', 'beta')})]
|
|
27
|
-
expect(getTagSelectOptions(tags)).toEqual([
|
|
28
|
-
{label: 'alpha', value: 't1'},
|
|
29
|
-
{label: 'beta', value: 't2'},
|
|
30
|
-
])
|
|
31
|
-
})
|
|
32
|
-
|
|
33
|
-
it('returns an empty array for an empty list', () => {
|
|
34
|
-
expect(getTagSelectOptions([])).toEqual([])
|
|
35
|
-
})
|
|
36
|
-
|
|
37
|
-
it('skips items without a tag', () => {
|
|
38
|
-
const tags = [
|
|
39
|
-
tagItem({tag: makeTag('t1', 'ok')}),
|
|
40
|
-
{_type: 'tag', tag: undefined, picked: false, updating: false} as unknown as TagItem,
|
|
41
|
-
]
|
|
42
|
-
expect(getTagSelectOptions(tags)).toEqual([{label: 'ok', value: 't1'}])
|
|
43
|
-
})
|
|
44
|
-
})
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import type {TagSelectOption, TagItem} from '../types'
|
|
2
|
-
|
|
3
|
-
const getTagSelectOptions = (tags: TagItem[]): TagSelectOption[] => {
|
|
4
|
-
return tags.reduce((acc: TagSelectOption[], val) => {
|
|
5
|
-
const tag = val?.tag
|
|
6
|
-
if (tag) {
|
|
7
|
-
acc.push({
|
|
8
|
-
label: tag?.name?.current,
|
|
9
|
-
value: tag?._id,
|
|
10
|
-
})
|
|
11
|
-
}
|
|
12
|
-
return acc
|
|
13
|
-
}, [])
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export default getTagSelectOptions
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import type {SanityDocument} from '@sanity/client'
|
|
2
|
-
import {describe, expect, it} from 'vitest'
|
|
3
|
-
|
|
4
|
-
import {getUniqueDocuments} from './getUniqueDocuments'
|
|
5
|
-
|
|
6
|
-
describe('getUniqueDocuments', () => {
|
|
7
|
-
it('drops published documents when a drafts.* sibling exists', () => {
|
|
8
|
-
const docs: SanityDocument[] = [
|
|
9
|
-
{_id: 'drafts.post1', _type: 'post'} as SanityDocument,
|
|
10
|
-
{_id: 'post1', _type: 'post'} as SanityDocument,
|
|
11
|
-
]
|
|
12
|
-
expect(getUniqueDocuments(docs)).toEqual([{_id: 'drafts.post1', _type: 'post'}])
|
|
13
|
-
})
|
|
14
|
-
|
|
15
|
-
it('keeps published-only and draft-only ids', () => {
|
|
16
|
-
const docs: SanityDocument[] = [
|
|
17
|
-
{_id: 'onlyPub', _type: 'x'} as SanityDocument,
|
|
18
|
-
{_id: 'drafts.onlyDraft', _type: 'x'} as SanityDocument,
|
|
19
|
-
]
|
|
20
|
-
expect(getUniqueDocuments(docs)).toEqual(docs)
|
|
21
|
-
})
|
|
22
|
-
|
|
23
|
-
it('returns an empty array for an empty list', () => {
|
|
24
|
-
expect(getUniqueDocuments([])).toEqual([])
|
|
25
|
-
})
|
|
26
|
-
})
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import type {SanityDocument} from '@sanity/client'
|
|
2
|
-
|
|
3
|
-
export function getUniqueDocuments(documents: SanityDocument[]): SanityDocument[] {
|
|
4
|
-
const draftIds = documents.reduce(
|
|
5
|
-
(acc: string[], doc: SanityDocument) =>
|
|
6
|
-
doc._id.startsWith('drafts.') ? acc.concat(doc._id.slice(7)) : acc,
|
|
7
|
-
[],
|
|
8
|
-
)
|
|
9
|
-
|
|
10
|
-
const filteredDocuments: SanityDocument[] = documents.filter(
|
|
11
|
-
(doc: SanityDocument) => !draftIds.includes(doc._id),
|
|
12
|
-
)
|
|
13
|
-
|
|
14
|
-
return filteredDocuments
|
|
15
|
-
}
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import {afterEach, describe, expect, it} from 'vitest'
|
|
2
|
-
|
|
3
|
-
import type {ImageAsset} from '../types'
|
|
4
|
-
import imageDprUrl from './imageDprUrl'
|
|
5
|
-
|
|
6
|
-
const asset = {
|
|
7
|
-
_id: 'a1',
|
|
8
|
-
_type: 'sanity.imageAsset',
|
|
9
|
-
_createdAt: '',
|
|
10
|
-
_updatedAt: '',
|
|
11
|
-
_rev: 'r1',
|
|
12
|
-
originalFilename: 'x.png',
|
|
13
|
-
size: 1,
|
|
14
|
-
mimeType: 'image/png',
|
|
15
|
-
url: 'https://cdn.test/image.png',
|
|
16
|
-
metadata: {dimensions: {width: 100, height: 100}, isOpaque: true},
|
|
17
|
-
} as ImageAsset
|
|
18
|
-
|
|
19
|
-
describe('imageDprUrl', () => {
|
|
20
|
-
const dpr = window.devicePixelRatio
|
|
21
|
-
|
|
22
|
-
afterEach(() => {
|
|
23
|
-
Object.defineProperty(window, 'devicePixelRatio', {value: dpr, configurable: true})
|
|
24
|
-
})
|
|
25
|
-
|
|
26
|
-
it('scales width by devicePixelRatio and sets fit=max', () => {
|
|
27
|
-
Object.defineProperty(window, 'devicePixelRatio', {value: 2, configurable: true})
|
|
28
|
-
const url = imageDprUrl(asset, {width: 400})
|
|
29
|
-
expect(url).toBe('https://cdn.test/image.png?fit=max&w=800')
|
|
30
|
-
})
|
|
31
|
-
|
|
32
|
-
it('includes height when provided, scaled by dpr', () => {
|
|
33
|
-
Object.defineProperty(window, 'devicePixelRatio', {value: 2, configurable: true})
|
|
34
|
-
const url = imageDprUrl(asset, {width: 300, height: 200})
|
|
35
|
-
expect(url).toBe('https://cdn.test/image.png?fit=max&w=600&h=400')
|
|
36
|
-
})
|
|
37
|
-
|
|
38
|
-
it('uses multiplier 1 when devicePixelRatio is missing', () => {
|
|
39
|
-
Object.defineProperty(window, 'devicePixelRatio', {
|
|
40
|
-
value: undefined as unknown as number,
|
|
41
|
-
configurable: true,
|
|
42
|
-
})
|
|
43
|
-
const url = imageDprUrl(asset, {width: 100})
|
|
44
|
-
expect(url).toBe('https://cdn.test/image.png?fit=max&w=100')
|
|
45
|
-
})
|
|
46
|
-
})
|
package/src/utils/imageDprUrl.ts
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import type {ImageAsset} from '../types'
|
|
2
|
-
|
|
3
|
-
const imageDprUrl = (
|
|
4
|
-
asset: ImageAsset,
|
|
5
|
-
options: {
|
|
6
|
-
width: number
|
|
7
|
-
height?: number
|
|
8
|
-
},
|
|
9
|
-
): string => {
|
|
10
|
-
const dpi =
|
|
11
|
-
typeof window === 'undefined' || !window.devicePixelRatio
|
|
12
|
-
? 1
|
|
13
|
-
: Math.round(window.devicePixelRatio)
|
|
14
|
-
const imgH = options?.height ? options?.height * Math.max(1, dpi) : undefined
|
|
15
|
-
const imgW = options.width * Math.max(1, dpi)
|
|
16
|
-
|
|
17
|
-
const urlParams = new URLSearchParams()
|
|
18
|
-
urlParams.append('fit', 'max')
|
|
19
|
-
urlParams.append('w', imgW.toString())
|
|
20
|
-
if (imgH) {
|
|
21
|
-
urlParams.append('h', imgH.toString())
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
return `${asset.url}?${urlParams.toString()}`
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export default imageDprUrl
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import {describe, expect, it} from 'vitest'
|
|
2
|
-
|
|
3
|
-
import {isSupportedAssetType} from './isSupportedAssetType'
|
|
4
|
-
|
|
5
|
-
describe('isSupportedAssetType', () => {
|
|
6
|
-
it('returns true for file and image', () => {
|
|
7
|
-
expect(isSupportedAssetType('file')).toBe(true)
|
|
8
|
-
expect(isSupportedAssetType('image')).toBe(true)
|
|
9
|
-
})
|
|
10
|
-
|
|
11
|
-
it('returns false for unsupported or missing types', () => {
|
|
12
|
-
expect(isSupportedAssetType('video')).toBe(false)
|
|
13
|
-
expect(isSupportedAssetType('')).toBe(false)
|
|
14
|
-
expect(isSupportedAssetType(undefined)).toBe(false)
|
|
15
|
-
})
|
|
16
|
-
})
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import {SUPPORTED_ASSET_TYPES} from '../constants'
|
|
2
|
-
import type {AssetType} from '../types'
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Determines whether or not the provided asset type (eg 'image', 'file', 'arbitrary')
|
|
6
|
-
* is a supported asset type for this plugin.
|
|
7
|
-
*
|
|
8
|
-
* @param assetType - The asset type to check.
|
|
9
|
-
* @returns True if the asset type is supported, false otherwise.
|
|
10
|
-
* @internal
|
|
11
|
-
*/
|
|
12
|
-
export function isSupportedAssetType(assetType?: string): assetType is AssetType {
|
|
13
|
-
const supported: string[] = SUPPORTED_ASSET_TYPES
|
|
14
|
-
return assetType ? supported.includes(assetType) : false
|
|
15
|
-
}
|
package/src/utils/mediaField.ts
DELETED
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
FieldDefinitionBase,
|
|
3
|
-
FileDefinition,
|
|
4
|
-
ImageDefinition,
|
|
5
|
-
WidenInitialValue,
|
|
6
|
-
WidenValidation,
|
|
7
|
-
} from 'sanity'
|
|
8
|
-
|
|
9
|
-
import {AutoTagInput} from '../components/AutoTagInputWrapper'
|
|
10
|
-
|
|
11
|
-
type ImageMediaFieldConfig = Omit<ImageDefinition, 'options'> &
|
|
12
|
-
FieldDefinitionBase & {
|
|
13
|
-
name: string
|
|
14
|
-
mediaTags: string[]
|
|
15
|
-
options?: ImageDefinition['options']
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
type FileMediaFieldConfig = Omit<FileDefinition, 'options'> &
|
|
19
|
-
FieldDefinitionBase & {
|
|
20
|
-
name: string
|
|
21
|
-
mediaTags: string[]
|
|
22
|
-
options?: FileDefinition['options']
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
type ImageMediaFieldResult = Omit<ImageDefinition, 'options'> &
|
|
26
|
-
FieldDefinitionBase & {
|
|
27
|
-
options?: ImageDefinition['options'] & {mediaTags: string[]}
|
|
28
|
-
components: {input: typeof AutoTagInput}
|
|
29
|
-
} & WidenValidation &
|
|
30
|
-
WidenInitialValue
|
|
31
|
-
|
|
32
|
-
type FileMediaFieldResult = Omit<FileDefinition, 'options'> &
|
|
33
|
-
FieldDefinitionBase & {
|
|
34
|
-
options?: FileDefinition['options'] & {mediaTags: string[]}
|
|
35
|
-
components: {input: typeof AutoTagInput}
|
|
36
|
-
} & WidenValidation &
|
|
37
|
-
WidenInitialValue
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Defines an image or file field with automatic media tag application when an asset is selected.
|
|
41
|
-
*
|
|
42
|
-
* Pass `mediaTags` at the top level — they are moved into `options.mediaTags` (for media browser
|
|
43
|
-
* pre-filtering) and wire up {@link AutoTagInput} as the field component automatically:
|
|
44
|
-
* ```ts
|
|
45
|
-
* import {mediaField} from 'sanity-plugin-media'
|
|
46
|
-
*
|
|
47
|
-
* mediaField({
|
|
48
|
-
* name: 'coverImage',
|
|
49
|
-
* type: 'image',
|
|
50
|
-
* mediaTags: ['product-cover'],
|
|
51
|
-
* options: { hotspot: true },
|
|
52
|
-
* })
|
|
53
|
-
* ```
|
|
54
|
-
*
|
|
55
|
-
* For file fields, set `type: 'file'`:
|
|
56
|
-
* ```ts
|
|
57
|
-
* mediaField({ name: 'drawing', type: 'file', mediaTags: ['model-drawing'] })
|
|
58
|
-
* ```
|
|
59
|
-
*/
|
|
60
|
-
export function mediaField(config: ImageMediaFieldConfig): ImageMediaFieldResult
|
|
61
|
-
export function mediaField(config: FileMediaFieldConfig): FileMediaFieldResult
|
|
62
|
-
export function mediaField(
|
|
63
|
-
config: ImageMediaFieldConfig | FileMediaFieldConfig,
|
|
64
|
-
): ImageMediaFieldResult | FileMediaFieldResult {
|
|
65
|
-
const {mediaTags, options, components, ...rest} = config as ImageMediaFieldConfig & {
|
|
66
|
-
components?: Record<string, unknown>
|
|
67
|
-
}
|
|
68
|
-
return {
|
|
69
|
-
...rest,
|
|
70
|
-
options: {...options, mediaTags},
|
|
71
|
-
components: {...components, input: AutoTagInput},
|
|
72
|
-
} as unknown as ImageMediaFieldResult
|
|
73
|
-
}
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
// @vitest-environment node
|
|
2
|
-
|
|
3
|
-
import {describe, expect, it} from 'vitest'
|
|
4
|
-
|
|
5
|
-
import sanitizeFormData from './sanitizeFormData'
|
|
6
|
-
|
|
7
|
-
describe('sanitizeFormData', () => {
|
|
8
|
-
it('maps empty string, undefined, and empty array to null', () => {
|
|
9
|
-
expect(
|
|
10
|
-
sanitizeFormData({
|
|
11
|
-
a: '',
|
|
12
|
-
b: undefined,
|
|
13
|
-
c: [],
|
|
14
|
-
}),
|
|
15
|
-
).toEqual({
|
|
16
|
-
a: null,
|
|
17
|
-
b: null,
|
|
18
|
-
c: null,
|
|
19
|
-
})
|
|
20
|
-
})
|
|
21
|
-
|
|
22
|
-
it('trims non-empty strings', () => {
|
|
23
|
-
expect(sanitizeFormData({title: ' hello '})).toEqual({title: 'hello'})
|
|
24
|
-
})
|
|
25
|
-
|
|
26
|
-
it('recurses into plain objects', () => {
|
|
27
|
-
expect(
|
|
28
|
-
sanitizeFormData({
|
|
29
|
-
opt: {
|
|
30
|
-
media: {
|
|
31
|
-
tags: [],
|
|
32
|
-
},
|
|
33
|
-
},
|
|
34
|
-
}),
|
|
35
|
-
).toEqual({
|
|
36
|
-
opt: {
|
|
37
|
-
media: {
|
|
38
|
-
tags: null,
|
|
39
|
-
},
|
|
40
|
-
},
|
|
41
|
-
})
|
|
42
|
-
})
|
|
43
|
-
|
|
44
|
-
it('preserves null and non-empty arrays', () => {
|
|
45
|
-
expect(
|
|
46
|
-
sanitizeFormData({
|
|
47
|
-
kept: null,
|
|
48
|
-
tags: [{_ref: 't1'}],
|
|
49
|
-
}),
|
|
50
|
-
).toEqual({
|
|
51
|
-
kept: null,
|
|
52
|
-
tags: [{_ref: 't1'}],
|
|
53
|
-
})
|
|
54
|
-
})
|
|
55
|
-
|
|
56
|
-
it('preserves numbers and booleans', () => {
|
|
57
|
-
expect(sanitizeFormData({n: 0, ok: false})).toEqual({n: 0, ok: false})
|
|
58
|
-
})
|
|
59
|
-
})
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
// Recursively sanitize form data:
|
|
2
|
-
// - convert empty strings, undefined values and empty arrays to null (to correctly unset / delete fields)
|
|
3
|
-
// - trim whitespace on string fleids
|
|
4
|
-
|
|
5
|
-
type FormData = Record<string, any>
|
|
6
|
-
|
|
7
|
-
const sanitizeFormData = (formData: FormData): FormData => {
|
|
8
|
-
return Object.keys(formData).reduce((acc: FormData, key) => {
|
|
9
|
-
const val = formData[key]
|
|
10
|
-
|
|
11
|
-
// TODO: refactor
|
|
12
|
-
if (typeof val === 'object' && val !== null && val.constructor !== Array) {
|
|
13
|
-
acc[key] = sanitizeFormData(val)
|
|
14
|
-
} else if (val === '' || typeof val === 'undefined' || val?.length === 0) {
|
|
15
|
-
acc[key] = null
|
|
16
|
-
} else if (typeof val === 'string' && val) {
|
|
17
|
-
acc[key] = formData[key].trim()
|
|
18
|
-
} else {
|
|
19
|
-
acc[key] = formData[key]
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
return acc
|
|
23
|
-
}, {})
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export default sanitizeFormData
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import {describe, expect, it} from 'vitest'
|
|
2
|
-
|
|
3
|
-
import type {Asset} from '../types'
|
|
4
|
-
import {isFileAsset, isImageAsset} from './typeGuards'
|
|
5
|
-
|
|
6
|
-
describe('typeGuards', () => {
|
|
7
|
-
it('isFileAsset narrows sanity.fileAsset', () => {
|
|
8
|
-
const file = {_type: 'sanity.fileAsset'} as Asset
|
|
9
|
-
expect(isFileAsset(file)).toBe(true)
|
|
10
|
-
expect(isImageAsset(file)).toBe(false)
|
|
11
|
-
})
|
|
12
|
-
|
|
13
|
-
it('isImageAsset narrows sanity.imageAsset', () => {
|
|
14
|
-
const image = {_type: 'sanity.imageAsset'} as Asset
|
|
15
|
-
expect(isImageAsset(image)).toBe(true)
|
|
16
|
-
expect(isFileAsset(image)).toBe(false)
|
|
17
|
-
})
|
|
18
|
-
})
|
package/src/utils/typeGuards.ts
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import type {Asset, FileAsset, ImageAsset} from '../types'
|
|
2
|
-
|
|
3
|
-
export const isFileAsset = (asset: Asset): asset is FileAsset => {
|
|
4
|
-
return (asset as FileAsset)._type === 'sanity.fileAsset'
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
export const isImageAsset = (asset: Asset): asset is ImageAsset => {
|
|
8
|
-
return (asset as ImageAsset)._type === 'sanity.imageAsset'
|
|
9
|
-
}
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import {firstValueFrom} from 'rxjs'
|
|
2
|
-
import {afterEach, describe, expect, it} from 'vitest'
|
|
3
|
-
|
|
4
|
-
import {hashFile$} from './uploadSanityAsset'
|
|
5
|
-
|
|
6
|
-
describe('hashFile$', () => {
|
|
7
|
-
const cryptoRef = globalThis.crypto
|
|
8
|
-
|
|
9
|
-
afterEach(() => {
|
|
10
|
-
Object.defineProperty(globalThis, 'crypto', {
|
|
11
|
-
value: cryptoRef,
|
|
12
|
-
configurable: true,
|
|
13
|
-
writable: true,
|
|
14
|
-
})
|
|
15
|
-
})
|
|
16
|
-
|
|
17
|
-
it('errors when Web Crypto is unavailable', async () => {
|
|
18
|
-
Object.defineProperty(globalThis, 'crypto', {
|
|
19
|
-
value: undefined,
|
|
20
|
-
configurable: true,
|
|
21
|
-
writable: true,
|
|
22
|
-
})
|
|
23
|
-
|
|
24
|
-
await expect(firstValueFrom(hashFile$(new File(['x'], 'blob.bin')))).rejects.toMatchObject({
|
|
25
|
-
message: expect.stringMatching(/secure contexts/i),
|
|
26
|
-
statusCode: 500,
|
|
27
|
-
})
|
|
28
|
-
})
|
|
29
|
-
})
|
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
// Sourced from:
|
|
2
|
-
// https://github.com/sanity-io/sanity/blob/ccb777e115a8cdf20d81a9a2bc9d8c228568faff/packages/%40sanity/form-builder/src/sanity/inputs/client-adapters/assets.ts
|
|
3
|
-
|
|
4
|
-
import type {SanityAssetDocument, SanityClient, SanityImageAssetDocument} from '@sanity/client'
|
|
5
|
-
import {Observable, of, throwError} from 'rxjs'
|
|
6
|
-
import {map, mergeMap} from 'rxjs/operators'
|
|
7
|
-
|
|
8
|
-
import type {HttpError} from '../types'
|
|
9
|
-
import {withMaxConcurrency} from './withMaxConcurrency'
|
|
10
|
-
|
|
11
|
-
const fetchExisting$ = (client: SanityClient, type: string, hash: string) => {
|
|
12
|
-
return client.observable.fetch('*[_type == $documentType && sha1hash == $hash][0]', {
|
|
13
|
-
documentType: type,
|
|
14
|
-
hash,
|
|
15
|
-
})
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
const readFile$ = (file: File): Observable<ArrayBuffer> => {
|
|
19
|
-
return new Observable((subscriber) => {
|
|
20
|
-
const reader = new FileReader()
|
|
21
|
-
reader.onload = () => {
|
|
22
|
-
subscriber.next(reader.result as ArrayBuffer)
|
|
23
|
-
subscriber.complete()
|
|
24
|
-
}
|
|
25
|
-
reader.onerror = (err) => {
|
|
26
|
-
subscriber.error(err)
|
|
27
|
-
}
|
|
28
|
-
reader.readAsArrayBuffer(file)
|
|
29
|
-
return () => {
|
|
30
|
-
reader.abort()
|
|
31
|
-
}
|
|
32
|
-
})
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
const hexFromBuffer = (buffer: ArrayBuffer): string => {
|
|
36
|
-
return Array.prototype.map
|
|
37
|
-
.call(new Uint8Array(buffer), (x) => `00${x.toString(16)}`.slice(-2))
|
|
38
|
-
.join('')
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export const hashFile$ = (file: File): Observable<string> => {
|
|
42
|
-
if (!window.crypto || !window.crypto.subtle || !window.FileReader) {
|
|
43
|
-
return throwError({
|
|
44
|
-
message: 'Unable to generate hash: uploads are only allowed in secure contexts',
|
|
45
|
-
statusCode: 500,
|
|
46
|
-
})
|
|
47
|
-
}
|
|
48
|
-
return readFile$(file).pipe(
|
|
49
|
-
mergeMap((arrayBuffer) => window.crypto.subtle.digest('SHA-1', arrayBuffer)),
|
|
50
|
-
map(hexFromBuffer),
|
|
51
|
-
)
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
const uploadSanityAsset$ = (
|
|
55
|
-
client: SanityClient,
|
|
56
|
-
assetType: 'file' | 'image',
|
|
57
|
-
file: File,
|
|
58
|
-
hash: string,
|
|
59
|
-
) => {
|
|
60
|
-
return of(null).pipe(
|
|
61
|
-
// NOTE: the sanity api will still dedupe unique files, but this saves us from uploading the asset file entirely
|
|
62
|
-
mergeMap(() => fetchExisting$(client, `sanity.${assetType}Asset`, hash)),
|
|
63
|
-
// Cancel if the asset already exists
|
|
64
|
-
mergeMap((existingAsset: SanityAssetDocument | SanityImageAssetDocument | null) => {
|
|
65
|
-
if (existingAsset) {
|
|
66
|
-
return throwError({
|
|
67
|
-
message: 'Asset already exists',
|
|
68
|
-
statusCode: 409,
|
|
69
|
-
} as HttpError)
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
return of(null)
|
|
73
|
-
}),
|
|
74
|
-
mergeMap(() => {
|
|
75
|
-
// Begin upload if no existing asset found
|
|
76
|
-
return client.observable.assets
|
|
77
|
-
.upload(assetType, file, {
|
|
78
|
-
extract: ['blurhash', 'exif', 'location', 'lqip', 'palette'],
|
|
79
|
-
preserveFilename: true,
|
|
80
|
-
})
|
|
81
|
-
.pipe(
|
|
82
|
-
map((event) =>
|
|
83
|
-
event.type === 'response'
|
|
84
|
-
? {
|
|
85
|
-
// rewrite to a 'complete' event
|
|
86
|
-
asset: event.body.document,
|
|
87
|
-
id: event.body.document._id,
|
|
88
|
-
type: 'complete',
|
|
89
|
-
}
|
|
90
|
-
: event,
|
|
91
|
-
),
|
|
92
|
-
)
|
|
93
|
-
}),
|
|
94
|
-
)
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
export const uploadAsset$ = withMaxConcurrency(uploadSanityAsset$)
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import {Observable, firstValueFrom} from 'rxjs'
|
|
2
|
-
import {describe, expect, it} from 'vitest'
|
|
3
|
-
|
|
4
|
-
import {createThrottler, withMaxConcurrency} from './withMaxConcurrency'
|
|
5
|
-
|
|
6
|
-
describe('createThrottler', () => {
|
|
7
|
-
it('never runs more observables concurrently than the limit', async () => {
|
|
8
|
-
let active = 0
|
|
9
|
-
let maxActive = 0
|
|
10
|
-
const request = createThrottler(2)
|
|
11
|
-
|
|
12
|
-
const mk = () =>
|
|
13
|
-
new Observable<number>((sub) => {
|
|
14
|
-
active++
|
|
15
|
-
maxActive = Math.max(maxActive, active)
|
|
16
|
-
queueMicrotask(() => {
|
|
17
|
-
active--
|
|
18
|
-
sub.next(1)
|
|
19
|
-
sub.complete()
|
|
20
|
-
})
|
|
21
|
-
})
|
|
22
|
-
|
|
23
|
-
await Promise.all([
|
|
24
|
-
firstValueFrom(request(mk())),
|
|
25
|
-
firstValueFrom(request(mk())),
|
|
26
|
-
firstValueFrom(request(mk())),
|
|
27
|
-
])
|
|
28
|
-
|
|
29
|
-
expect(maxActive).toBe(2)
|
|
30
|
-
})
|
|
31
|
-
})
|
|
32
|
-
|
|
33
|
-
describe('withMaxConcurrency', () => {
|
|
34
|
-
it('wraps a function so each call returns a single-value observable', async () => {
|
|
35
|
-
const fn = (n: number) =>
|
|
36
|
-
new Observable<number>((sub) => {
|
|
37
|
-
sub.next(n)
|
|
38
|
-
sub.complete()
|
|
39
|
-
})
|
|
40
|
-
const wrapped = withMaxConcurrency(fn, 4)
|
|
41
|
-
await expect(firstValueFrom(wrapped(7))).resolves.toBe(7)
|
|
42
|
-
})
|
|
43
|
-
})
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
import {Subject, Subscription, Observable, from} from 'rxjs'
|
|
2
|
-
// Takes a observable-returning function and returns a new function that limits on the number of
|
|
3
|
-
// concurrent observables.
|
|
4
|
-
import {first, mergeMap} from 'rxjs/operators'
|
|
5
|
-
|
|
6
|
-
const DEFAULT_CONCURRENCY = 4
|
|
7
|
-
|
|
8
|
-
function remove<T>(array: Array<T>, item: T): Array<T> {
|
|
9
|
-
const index = array.indexOf(item)
|
|
10
|
-
if (index > -1) {
|
|
11
|
-
array.splice(index, 1)
|
|
12
|
-
}
|
|
13
|
-
return array
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export const createThrottler = (concurrency: number = DEFAULT_CONCURRENCY) => {
|
|
17
|
-
const currentSubscriptions: Array<Subscription> = []
|
|
18
|
-
const pendingObservables: Array<Observable<any>> = []
|
|
19
|
-
const ready$ = new Subject()
|
|
20
|
-
|
|
21
|
-
function request(observable: Observable<any>): Observable<any> {
|
|
22
|
-
return new Observable((observer) => {
|
|
23
|
-
if (currentSubscriptions.length >= concurrency) {
|
|
24
|
-
return scheduleAndWait$(observable)
|
|
25
|
-
.pipe(mergeMap(request)) //
|
|
26
|
-
.subscribe(observer)
|
|
27
|
-
}
|
|
28
|
-
const subscription = observable.subscribe(observer)
|
|
29
|
-
currentSubscriptions.push(subscription)
|
|
30
|
-
return () => {
|
|
31
|
-
remove(currentSubscriptions, subscription)
|
|
32
|
-
remove(pendingObservables, observable)
|
|
33
|
-
subscription.unsubscribe()
|
|
34
|
-
while (pendingObservables.length > 0 && currentSubscriptions.length < concurrency) {
|
|
35
|
-
ready$.next(pendingObservables.shift())
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
})
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function scheduleAndWait$(observable: Observable<any>): Observable<any> {
|
|
42
|
-
pendingObservables.push(observable)
|
|
43
|
-
return ready$.asObservable().pipe(first((obs) => obs === observable))
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
return request
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export const withMaxConcurrency = (
|
|
50
|
-
func: (...args: any[]) => Observable<any>,
|
|
51
|
-
concurrency: number = DEFAULT_CONCURRENCY,
|
|
52
|
-
) => {
|
|
53
|
-
const throttler = createThrottler(concurrency)
|
|
54
|
-
return (...args: Array<any>) => from(throttler(func(...args)))
|
|
55
|
-
}
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import {zodResolver} from '@hookform/resolvers/zod'
|
|
2
|
-
import type {FieldValues, Resolver} from 'react-hook-form'
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* `@hookform/resolvers/zod` resolves its own `zod` typings through dependency
|
|
6
|
-
* hoisting, which in this monorepo lands on zod v4, while this plugin authors its
|
|
7
|
-
* form schemas with zod v3. The resolver accepts v3 schema instances correctly at
|
|
8
|
-
* runtime, so this thin wrapper only bridges the type-only v3/v4 mismatch. The
|
|
9
|
-
* react-hook-form field types stay fully checked at the call sites.
|
|
10
|
-
*/
|
|
11
|
-
export default function zodFormResolver<TFieldValues extends FieldValues>(
|
|
12
|
-
schema: unknown,
|
|
13
|
-
): Resolver<TFieldValues> {
|
|
14
|
-
return zodResolver(
|
|
15
|
-
schema as Parameters<typeof zodResolver>[0],
|
|
16
|
-
) as unknown as Resolver<TFieldValues>
|
|
17
|
-
}
|
package/v2-incompatible.js
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
const {showIncompatiblePluginDialog} = require('@sanity/incompatible-plugin')
|
|
2
|
-
const {name, version} = require('./package.json')
|
|
3
|
-
|
|
4
|
-
export default showIncompatiblePluginDialog({
|
|
5
|
-
name: name,
|
|
6
|
-
versions: {
|
|
7
|
-
v3: version,
|
|
8
|
-
v2: '^1.4.13',
|
|
9
|
-
},
|
|
10
|
-
sanityExchangeUrl: 'https://www.sanity.io/plugins/sanity-plugin-media',
|
|
11
|
-
})
|