sanity-plugin-media 4.3.5 → 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +190 -413
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2532 -3564
- package/dist/index.js.map +1 -1
- package/package.json +29 -35
- package/dist/index.cjs +0 -5753
- package/dist/index.cjs.map +0 -1
- package/dist/index.d.cts +0 -462
- package/sanity.json +0 -8
- package/src/__tests__/fixtures/createEpicTestStore.ts +0 -28
- package/src/__tests__/fixtures/listenMock.ts +0 -9
- package/src/__tests__/fixtures/mockSanityClient.ts +0 -84
- package/src/__tests__/fixtures/renderWithProviders.tsx +0 -55
- package/src/__tests__/fixtures/rootState.ts +0 -27
- package/src/__tests__/fixtures/withinDialog.ts +0 -28
- package/src/components/AssetGridVirtualized/index.tsx +0 -94
- package/src/components/AssetMetadata/index.tsx +0 -122
- package/src/components/AssetTableVirtualized/index.tsx +0 -73
- package/src/components/AutoTagInputWrapper/index.tsx +0 -87
- package/src/components/Browser/Browser.test.tsx +0 -45
- package/src/components/Browser/index.tsx +0 -90
- package/src/components/Browser/useBrowserInit.ts +0 -126
- package/src/components/ButtonAssetCopy/index.tsx +0 -65
- package/src/components/ButtonViewGroup/index.tsx +0 -39
- package/src/components/CardAsset/CardAsset.test.tsx +0 -323
- package/src/components/CardAsset/index.tsx +0 -290
- package/src/components/CardUpload/index.tsx +0 -161
- package/src/components/Controls/index.tsx +0 -136
- package/src/components/DebugControls/index.tsx +0 -80
- package/src/components/Dialog/index.tsx +0 -11
- package/src/components/DialogAssetEdit/Details.tsx +0 -181
- package/src/components/DialogAssetEdit/DialogAssetEdit.test.tsx +0 -216
- package/src/components/DialogAssetEdit/index.tsx +0 -492
- package/src/components/DialogConfirm/index.tsx +0 -88
- package/src/components/DialogSearchFacets/index.tsx +0 -42
- package/src/components/DialogTagCreate/DialogTagCreate.test.tsx +0 -121
- package/src/components/DialogTagCreate/index.tsx +0 -103
- package/src/components/DialogTagEdit/DialogTagEdit.test.tsx +0 -165
- package/src/components/DialogTagEdit/index.tsx +0 -190
- package/src/components/DialogTags/index.tsx +0 -45
- package/src/components/Dialogs/index.tsx +0 -76
- package/src/components/DocumentList/index.tsx +0 -62
- package/src/components/FileAssetPreview/index.tsx +0 -37
- package/src/components/FileIcon/index.tsx +0 -43
- package/src/components/FormBuilderTool/FormBuilderTool.test.tsx +0 -63
- package/src/components/FormBuilderTool/index.tsx +0 -69
- package/src/components/FormFieldInputLabel/index.tsx +0 -66
- package/src/components/FormFieldInputTags/index.tsx +0 -98
- package/src/components/FormFieldInputText/index.tsx +0 -41
- package/src/components/FormFieldInputTextarea/index.tsx +0 -43
- package/src/components/FormSubmitButton/index.tsx +0 -59
- package/src/components/Header/index.tsx +0 -80
- package/src/components/Image/index.tsx +0 -41
- package/src/components/Items/index.tsx +0 -68
- package/src/components/Notifications/index.tsx +0 -24
- package/src/components/OrderSelect/index.tsx +0 -66
- package/src/components/PickedBar/index.tsx +0 -77
- package/src/components/Progress/index.tsx +0 -38
- package/src/components/ReduxProvider/index.tsx +0 -96
- package/src/components/SearchFacet/index.tsx +0 -66
- package/src/components/SearchFacetNumber/index.tsx +0 -133
- package/src/components/SearchFacetSelect/index.tsx +0 -110
- package/src/components/SearchFacetString/index.tsx +0 -88
- package/src/components/SearchFacetTags/index.tsx +0 -121
- package/src/components/SearchFacets/index.tsx +0 -72
- package/src/components/SearchFacetsControl/index.tsx +0 -140
- package/src/components/TableHeader/index.tsx +0 -110
- package/src/components/TableHeaderItem/index.tsx +0 -61
- package/src/components/TableRowAsset/index.tsx +0 -420
- package/src/components/TableRowUpload/index.tsx +0 -164
- package/src/components/Tag/index.tsx +0 -200
- package/src/components/TagIcon/index.tsx +0 -22
- package/src/components/TagView/index.tsx +0 -39
- package/src/components/TagViewHeader/index.tsx +0 -70
- package/src/components/TagsPanel/index.tsx +0 -40
- package/src/components/TagsVirtualized/index.tsx +0 -160
- package/src/components/TextInputNumber/index.tsx +0 -32
- package/src/components/TextInputSearch/index.tsx +0 -60
- package/src/components/Tool/index.tsx +0 -13
- package/src/components/UploadDropzone/UploadDropzone.test.tsx +0 -40
- package/src/components/UploadDropzone/index.tsx +0 -173
- package/src/config/orders.ts +0 -28
- package/src/config/searchFacets.ts +0 -312
- package/src/constants.ts +0 -87
- package/src/contexts/AssetSourceDispatchContext.tsx +0 -37
- package/src/contexts/DropzoneDispatchContext.tsx +0 -34
- package/src/contexts/ToolOptionsContext.tsx +0 -65
- package/src/formSchema/index.test.ts +0 -56
- package/src/formSchema/index.ts +0 -39
- package/src/hooks/useBreakpointIndex.ts +0 -49
- package/src/hooks/useKeyPress.ts +0 -39
- package/src/hooks/useOnScreen.ts +0 -34
- package/src/hooks/usePortalPopoverProps.ts +0 -13
- package/src/hooks/useTypedSelector.ts +0 -7
- package/src/hooks/useVersionedClient.ts +0 -6
- package/src/index.ts +0 -5
- package/src/modules/assets/actions.ts +0 -42
- package/src/modules/assets/deleteAndUpdateEpics.test.ts +0 -87
- package/src/modules/assets/fetchEpic.test.ts +0 -73
- package/src/modules/assets/index.ts +0 -825
- package/src/modules/assets/reducer.test.ts +0 -91
- package/src/modules/assets/tagsAndListenerEpics.test.ts +0 -206
- package/src/modules/debug/index.ts +0 -28
- package/src/modules/dialog/actions.ts +0 -10
- package/src/modules/dialog/epics.test.ts +0 -168
- package/src/modules/dialog/index.ts +0 -238
- package/src/modules/dialog/reducer.test.ts +0 -185
- package/src/modules/index.ts +0 -124
- package/src/modules/notifications/epics.test.ts +0 -374
- package/src/modules/notifications/index.ts +0 -199
- package/src/modules/notifications/reducer.test.ts +0 -54
- package/src/modules/search/index.test.ts +0 -36
- package/src/modules/search/index.ts +0 -167
- package/src/modules/selected/index.ts +0 -22
- package/src/modules/selectors.test.ts +0 -21
- package/src/modules/selectors.ts +0 -17
- package/src/modules/tags/epics.test.ts +0 -96
- package/src/modules/tags/index.test.ts +0 -42
- package/src/modules/tags/index.ts +0 -540
- package/src/modules/types.ts +0 -3
- package/src/modules/uploads/actions.ts +0 -13
- package/src/modules/uploads/epics.test.ts +0 -109
- package/src/modules/uploads/index.test.ts +0 -59
- package/src/modules/uploads/index.ts +0 -282
- package/src/operators/checkTagName.test.ts +0 -29
- package/src/operators/checkTagName.ts +0 -33
- package/src/operators/debugThrottle.ts +0 -25
- package/src/plugin.tsx +0 -54
- package/src/schemas/tag.ts +0 -28
- package/src/styled/GlobalStyles/index.tsx +0 -40
- package/src/styled/react-select/creatable.tsx +0 -184
- package/src/styled/react-select/single.tsx +0 -184
- package/src/types/index.ts +0 -379
- package/src/types/sanity-ui.d.ts +0 -6
- package/src/utils/applyMediaTags.ts +0 -87
- package/src/utils/blocksToText.test.ts +0 -43
- package/src/utils/blocksToText.ts +0 -27
- package/src/utils/constructFilter.test.ts +0 -120
- package/src/utils/constructFilter.ts +0 -98
- package/src/utils/generatePreviewBlobUrl.test.ts +0 -70
- package/src/utils/generatePreviewBlobUrl.ts +0 -53
- package/src/utils/getAssetResolution.test.ts +0 -13
- package/src/utils/getAssetResolution.ts +0 -7
- package/src/utils/getDocumentAssetIds.test.ts +0 -50
- package/src/utils/getDocumentAssetIds.ts +0 -35
- package/src/utils/getSchemeColor.test.ts +0 -12
- package/src/utils/getSchemeColor.ts +0 -43
- package/src/utils/getTagSelectOptions.test.ts +0 -44
- package/src/utils/getTagSelectOptions.ts +0 -16
- package/src/utils/getUniqueDocuments.test.ts +0 -26
- package/src/utils/getUniqueDocuments.ts +0 -15
- package/src/utils/imageDprUrl.test.ts +0 -46
- package/src/utils/imageDprUrl.ts +0 -27
- package/src/utils/isSupportedAssetType.test.ts +0 -16
- package/src/utils/isSupportedAssetType.ts +0 -15
- package/src/utils/mediaField.ts +0 -73
- package/src/utils/sanitizeFormData.test.ts +0 -59
- package/src/utils/sanitizeFormData.ts +0 -26
- package/src/utils/typeGuards.test.ts +0 -18
- package/src/utils/typeGuards.ts +0 -9
- package/src/utils/uploadSanityAsset.test.ts +0 -29
- package/src/utils/uploadSanityAsset.ts +0 -97
- package/src/utils/withMaxConcurrency.test.ts +0 -43
- package/src/utils/withMaxConcurrency.ts +0 -55
- package/src/utils/zodFormResolver.ts +0 -17
- package/v2-incompatible.js +0 -11
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
import groq from 'groq'
|
|
2
|
-
|
|
3
|
-
import {operators} from '../config/searchFacets'
|
|
4
|
-
import type {AssetType, SearchFacetInputProps} from '../types'
|
|
5
|
-
|
|
6
|
-
const constructFilter = ({
|
|
7
|
-
assetTypes,
|
|
8
|
-
searchFacets,
|
|
9
|
-
searchQuery,
|
|
10
|
-
}: {
|
|
11
|
-
assetTypes: AssetType[]
|
|
12
|
-
searchFacets: SearchFacetInputProps[]
|
|
13
|
-
searchQuery?: string
|
|
14
|
-
}): string => {
|
|
15
|
-
// Fetch asset types depending on current context.
|
|
16
|
-
// Either limit to a specific type (if being used as a custom asset source) or fetch both files and images (if being used as a tool)
|
|
17
|
-
// Sanity will crash if you try and insert incompatible asset types into fields!
|
|
18
|
-
const documentAssetTypes = assetTypes.map((type) => `sanity.${type}Asset`)
|
|
19
|
-
|
|
20
|
-
const baseFilter = groq`
|
|
21
|
-
_type in ${JSON.stringify(documentAssetTypes)} && !(_id in path("drafts.**"))
|
|
22
|
-
`
|
|
23
|
-
|
|
24
|
-
const searchFacetFragments = searchFacets.reduce((acc: string[], facet) => {
|
|
25
|
-
if (facet.type === 'number') {
|
|
26
|
-
const {field, modifier, modifiers, operatorType, value} = facet
|
|
27
|
-
const operator = operators[operatorType]
|
|
28
|
-
|
|
29
|
-
// Get current modifier
|
|
30
|
-
const currentModifier = modifiers?.find((m) => m.name === modifier)
|
|
31
|
-
|
|
32
|
-
// Apply field modifier function (if present)
|
|
33
|
-
const facetField = currentModifier?.fieldModifier
|
|
34
|
-
? currentModifier.fieldModifier(field)
|
|
35
|
-
: field
|
|
36
|
-
|
|
37
|
-
const fragment = operator.fn(value, facetField)
|
|
38
|
-
if (fragment) {
|
|
39
|
-
acc.push(fragment)
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
if (facet.type === 'searchable') {
|
|
44
|
-
const {field, operatorType, value} = facet
|
|
45
|
-
const operator = operators[operatorType]
|
|
46
|
-
|
|
47
|
-
const fragment = operator.fn(value?.value, field)
|
|
48
|
-
if (fragment) {
|
|
49
|
-
acc.push(fragment)
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
if (facet.type === 'select') {
|
|
54
|
-
const {field, operatorType, options, value} = facet
|
|
55
|
-
const operator = operators[operatorType]
|
|
56
|
-
|
|
57
|
-
const currentOptionValue = options?.find((l) => l.name === value)?.value
|
|
58
|
-
|
|
59
|
-
const fragment = operator.fn(currentOptionValue, field)
|
|
60
|
-
if (fragment) {
|
|
61
|
-
acc.push(fragment)
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
if (facet.type === 'string') {
|
|
66
|
-
const {field, operatorType, value} = facet
|
|
67
|
-
const operator = operators[operatorType]
|
|
68
|
-
|
|
69
|
-
const fragment = operator.fn(value, field)
|
|
70
|
-
if (fragment) {
|
|
71
|
-
acc.push(fragment)
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
return acc
|
|
76
|
-
}, [])
|
|
77
|
-
|
|
78
|
-
// Join separate filter fragments
|
|
79
|
-
const constructedQuery = [
|
|
80
|
-
// Base filter
|
|
81
|
-
baseFilter,
|
|
82
|
-
// Search query (if present)
|
|
83
|
-
// NOTE: Currently this only searches direct fields on sanity.fileAsset/sanity.imageAsset and NOT referenced tags
|
|
84
|
-
// It's possible to add this by adding the following line to the searchQuery, but it's quite slow
|
|
85
|
-
// references(*[_type == "media.tag" && name.current == "${searchQuery.trim()}"]._id)
|
|
86
|
-
...(searchQuery
|
|
87
|
-
? [
|
|
88
|
-
groq`[_id, altText, assetId, creditLine, description, originalFilename, title, url] match '*${searchQuery.trim()}*'`,
|
|
89
|
-
]
|
|
90
|
-
: []),
|
|
91
|
-
// Search facets
|
|
92
|
-
...searchFacetFragments,
|
|
93
|
-
].join(' && ')
|
|
94
|
-
|
|
95
|
-
return constructedQuery
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
export default constructFilter
|
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
import {firstValueFrom} from 'rxjs'
|
|
2
|
-
import {afterEach, beforeEach, describe, expect, it, vi} from 'vitest'
|
|
3
|
-
|
|
4
|
-
import {generatePreviewBlobUrl$} from './generatePreviewBlobUrl'
|
|
5
|
-
|
|
6
|
-
describe('generatePreviewBlobUrl$', () => {
|
|
7
|
-
const origCreateElement = document.createElement.bind(document)
|
|
8
|
-
|
|
9
|
-
beforeEach(() => {
|
|
10
|
-
class MockImage {
|
|
11
|
-
onload: (() => void) | null = null
|
|
12
|
-
width = 400
|
|
13
|
-
height = 200
|
|
14
|
-
private _src = ''
|
|
15
|
-
get src() {
|
|
16
|
-
return this._src
|
|
17
|
-
}
|
|
18
|
-
set src(v: string) {
|
|
19
|
-
this._src = v
|
|
20
|
-
queueMicrotask(() => this.onload?.())
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
vi.stubGlobal('Image', MockImage)
|
|
24
|
-
|
|
25
|
-
vi.spyOn(document, 'createElement').mockImplementation((tagName: string) => {
|
|
26
|
-
if (tagName === 'canvas') {
|
|
27
|
-
const el = origCreateElement('canvas')
|
|
28
|
-
vi.spyOn(el, 'getContext').mockReturnValue({
|
|
29
|
-
drawImage: vi.fn(),
|
|
30
|
-
} as unknown as CanvasRenderingContext2D)
|
|
31
|
-
/* eslint-disable callback-return, consistent-return -- HTMLCanvasElement#toBlob sync test stub */
|
|
32
|
-
el.toBlob = function toBlob(cb: ((blob: Blob | null) => void) | null | undefined) {
|
|
33
|
-
if (cb) {
|
|
34
|
-
cb(new Blob(['x'], {type: 'image/jpeg'}))
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
/* eslint-enable callback-return, consistent-return */
|
|
38
|
-
return el
|
|
39
|
-
}
|
|
40
|
-
return origCreateElement(tagName)
|
|
41
|
-
})
|
|
42
|
-
|
|
43
|
-
const createObjectURL = vi.fn(() => 'blob:mock-preview')
|
|
44
|
-
const revokeObjectURL = vi.fn()
|
|
45
|
-
Object.defineProperty(URL, 'createObjectURL', {
|
|
46
|
-
configurable: true,
|
|
47
|
-
writable: true,
|
|
48
|
-
value: createObjectURL,
|
|
49
|
-
})
|
|
50
|
-
Object.defineProperty(URL, 'revokeObjectURL', {
|
|
51
|
-
configurable: true,
|
|
52
|
-
writable: true,
|
|
53
|
-
value: revokeObjectURL,
|
|
54
|
-
})
|
|
55
|
-
})
|
|
56
|
-
|
|
57
|
-
afterEach(() => {
|
|
58
|
-
vi.unstubAllGlobals()
|
|
59
|
-
vi.restoreAllMocks()
|
|
60
|
-
delete (URL as Partial<typeof URL> & {createObjectURL?: unknown}).createObjectURL
|
|
61
|
-
delete (URL as Partial<typeof URL> & {revokeObjectURL?: unknown}).revokeObjectURL
|
|
62
|
-
})
|
|
63
|
-
|
|
64
|
-
it('emits a blob URL when canvas preview succeeds', async () => {
|
|
65
|
-
const url = await firstValueFrom(
|
|
66
|
-
generatePreviewBlobUrl$(new File(['x'], 'photo.jpg', {type: 'image/jpeg'})),
|
|
67
|
-
)
|
|
68
|
-
expect(url).toBe('blob:mock-preview')
|
|
69
|
-
})
|
|
70
|
-
})
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import {Observable, from, of} from 'rxjs'
|
|
2
|
-
import {mergeMap} from 'rxjs/operators'
|
|
3
|
-
|
|
4
|
-
const PREVIEW_WIDTH = 180 // px
|
|
5
|
-
|
|
6
|
-
const createBlob = (img: HTMLImageElement): Promise<Blob | null> => {
|
|
7
|
-
return new Promise((resolve) => {
|
|
8
|
-
const imageAspect = img.width / img.height
|
|
9
|
-
|
|
10
|
-
// Create a canvas element which we'll use to generate a low resolution preview.
|
|
11
|
-
// Ensure that the canvas is at least 1 pixel high as blob generation will fail otherwise.
|
|
12
|
-
const canvas: HTMLCanvasElement = document.createElement('canvas')
|
|
13
|
-
canvas.width = PREVIEW_WIDTH
|
|
14
|
-
canvas.height = Math.max(PREVIEW_WIDTH / imageAspect, 1)
|
|
15
|
-
|
|
16
|
-
// Fail silently if we're unable to generate a preview image.
|
|
17
|
-
// This can often be the case when trying to render SVGs containing `<foreignObject>` elements.
|
|
18
|
-
try {
|
|
19
|
-
const ctx = canvas.getContext('2d')
|
|
20
|
-
ctx?.drawImage(img, 0, 0, PREVIEW_WIDTH, PREVIEW_WIDTH / imageAspect)
|
|
21
|
-
canvas.toBlob(resolve, 'image/jpeg')
|
|
22
|
-
} catch (err) {
|
|
23
|
-
console.warn(`Unable to generate preview image:`, err)
|
|
24
|
-
}
|
|
25
|
-
})
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
const createImageEl = (file: File): Promise<HTMLImageElement> => {
|
|
29
|
-
return new Promise((resolve) => {
|
|
30
|
-
const blobUrlLarge = window.URL.createObjectURL(file)
|
|
31
|
-
const img = new Image()
|
|
32
|
-
img.onload = () => {
|
|
33
|
-
window.URL.revokeObjectURL(blobUrlLarge)
|
|
34
|
-
resolve(img)
|
|
35
|
-
}
|
|
36
|
-
img.src = blobUrlLarge
|
|
37
|
-
})
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
const generatePreviewBlobUrl = async (file: File): Promise<string> => {
|
|
41
|
-
const imageEl = await createImageEl(file)
|
|
42
|
-
const blob = await createBlob(imageEl)
|
|
43
|
-
|
|
44
|
-
if (!blob) {
|
|
45
|
-
throw Error('Unable to generate file Blob')
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
return window.URL.createObjectURL(blob)
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
export const generatePreviewBlobUrl$ = (file: File): Observable<string> => {
|
|
52
|
-
return of(null).pipe(mergeMap(() => from(generatePreviewBlobUrl(file))))
|
|
53
|
-
}
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import {describe, expect, it} from 'vitest'
|
|
2
|
-
|
|
3
|
-
import type {ImageAsset} from '../types'
|
|
4
|
-
import getAssetResolution from './getAssetResolution'
|
|
5
|
-
|
|
6
|
-
describe('getAssetResolution', () => {
|
|
7
|
-
it('formats width x height with px suffix', () => {
|
|
8
|
-
const asset = {
|
|
9
|
-
metadata: {dimensions: {width: 1920, height: 1080}},
|
|
10
|
-
} as ImageAsset
|
|
11
|
-
expect(getAssetResolution(asset)).toBe('1920x1080px')
|
|
12
|
-
})
|
|
13
|
-
})
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
// @vitest-environment node
|
|
2
|
-
|
|
3
|
-
import {describe, expect, it} from 'vitest'
|
|
4
|
-
|
|
5
|
-
import getDocumentAssetIds from './getDocumentAssetIds'
|
|
6
|
-
|
|
7
|
-
describe('getDocumentAssetIds', () => {
|
|
8
|
-
it('returns empty array for document without asset refs', () => {
|
|
9
|
-
expect(getDocumentAssetIds({_id: 'doc1', _type: 'post'} as any)).toEqual([])
|
|
10
|
-
})
|
|
11
|
-
|
|
12
|
-
it('collects asset _ref from nested portable text–like structures', () => {
|
|
13
|
-
const doc = {
|
|
14
|
-
_id: 'doc1',
|
|
15
|
-
_type: 'post',
|
|
16
|
-
body: [
|
|
17
|
-
{
|
|
18
|
-
_type: 'block',
|
|
19
|
-
asset: {_type: 'reference', _ref: 'image-asset-1'},
|
|
20
|
-
},
|
|
21
|
-
],
|
|
22
|
-
} as any
|
|
23
|
-
|
|
24
|
-
expect(getDocumentAssetIds(doc)).toEqual(['image-asset-1'])
|
|
25
|
-
})
|
|
26
|
-
|
|
27
|
-
it('dedupes and sorts refs', () => {
|
|
28
|
-
const doc = {
|
|
29
|
-
_id: 'doc1',
|
|
30
|
-
_type: 'post',
|
|
31
|
-
modules: [
|
|
32
|
-
{image: {asset: {_type: 'reference', _ref: 'b'}}},
|
|
33
|
-
{image: {asset: {_type: 'reference', _ref: 'a'}}},
|
|
34
|
-
{image: {asset: {_type: 'reference', _ref: 'b'}}},
|
|
35
|
-
],
|
|
36
|
-
} as any
|
|
37
|
-
|
|
38
|
-
expect(getDocumentAssetIds(doc)).toEqual(['a', 'b'])
|
|
39
|
-
})
|
|
40
|
-
|
|
41
|
-
it('ignores reference nodes that are not asset references', () => {
|
|
42
|
-
const doc = {
|
|
43
|
-
_id: 'doc1',
|
|
44
|
-
_type: 'post',
|
|
45
|
-
author: {_type: 'reference', _ref: 'person-1'},
|
|
46
|
-
} as any
|
|
47
|
-
|
|
48
|
-
expect(getDocumentAssetIds(doc)).toEqual([])
|
|
49
|
-
})
|
|
50
|
-
})
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import type {SanityDocument} from '@sanity/client'
|
|
2
|
-
|
|
3
|
-
const isPlainObject = (value: any) =>
|
|
4
|
-
value !== null && typeof value === 'object' && !Array.isArray(value)
|
|
5
|
-
|
|
6
|
-
// Recursively search node for any linked asset ids (`asset._type === 'reference'`)
|
|
7
|
-
const getAssetIds = (node: Record<string, any>, acc: string[] = []) => {
|
|
8
|
-
if (Array.isArray(node)) {
|
|
9
|
-
node.forEach((v) => {
|
|
10
|
-
getAssetIds(v, acc)
|
|
11
|
-
})
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
if (isPlainObject(node)) {
|
|
15
|
-
if (node?.asset?._type === 'reference' && node?.asset?._ref) {
|
|
16
|
-
acc.push(node.asset._ref)
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
Object.values(node).forEach((val) => {
|
|
20
|
-
getAssetIds(val, acc)
|
|
21
|
-
})
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
return acc
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
// Retrieve all linked asset ids from a Sanity document
|
|
28
|
-
const getDocumentAssetIds = (document: SanityDocument): string[] => {
|
|
29
|
-
const assetIds = getAssetIds(document)
|
|
30
|
-
|
|
31
|
-
// Sort and dedupe
|
|
32
|
-
return [...new Set(assetIds.sort())]
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export default getDocumentAssetIds
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import {describe, expect, it} from 'vitest'
|
|
2
|
-
|
|
3
|
-
import {getSchemeColor} from './getSchemeColor'
|
|
4
|
-
|
|
5
|
-
describe('getSchemeColor', () => {
|
|
6
|
-
it('returns a hex or theme string for light and dark schemes', () => {
|
|
7
|
-
expect(getSchemeColor('light', 'bg')).toMatch(/^#/)
|
|
8
|
-
expect(getSchemeColor('dark', 'bg')).toMatch(/^#/)
|
|
9
|
-
expect(getSchemeColor('light', 'spotBlue')).toBeTruthy()
|
|
10
|
-
expect(getSchemeColor('dark', 'inputEnabledBorder')).toBeTruthy()
|
|
11
|
-
})
|
|
12
|
-
})
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import {hues} from '@sanity/color'
|
|
2
|
-
import {type ThemeColorSchemeKey, studioTheme} from '@sanity/ui'
|
|
3
|
-
|
|
4
|
-
const SCHEME_COLORS = {
|
|
5
|
-
bg: {
|
|
6
|
-
dark: hues.gray[950].hex,
|
|
7
|
-
light: hues.gray[50].hex,
|
|
8
|
-
},
|
|
9
|
-
bg2: {
|
|
10
|
-
dark: hues.gray[900].hex,
|
|
11
|
-
light: hues.gray[100].hex,
|
|
12
|
-
},
|
|
13
|
-
inputEnabledBorder: {
|
|
14
|
-
dark: studioTheme.color.dark.default.input.default.enabled.border,
|
|
15
|
-
light: studioTheme.color.light.default.input.default.enabled.border,
|
|
16
|
-
},
|
|
17
|
-
inputHoveredBorder: {
|
|
18
|
-
dark: studioTheme.color.dark.default.input.default.hovered.border,
|
|
19
|
-
light: studioTheme.color.light.default.input.default.hovered.border,
|
|
20
|
-
},
|
|
21
|
-
mutedHoveredBg: {
|
|
22
|
-
dark: studioTheme.color.dark.primary.muted.primary.hovered.bg,
|
|
23
|
-
light: studioTheme.color.light.primary.muted.primary.hovered.bg,
|
|
24
|
-
},
|
|
25
|
-
mutedHoveredFg: {
|
|
26
|
-
dark: studioTheme.color.dark.primary.muted.primary.hovered.fg,
|
|
27
|
-
light: studioTheme.color.light.primary.muted.primary.hovered.fg,
|
|
28
|
-
},
|
|
29
|
-
mutedSelectedBg: {
|
|
30
|
-
dark: studioTheme.color.dark.primary.muted.primary.selected.bg,
|
|
31
|
-
light: studioTheme.color.light.primary.muted.primary.selected.bg,
|
|
32
|
-
},
|
|
33
|
-
spotBlue: {
|
|
34
|
-
dark: studioTheme.color.dark.primary.spot.blue,
|
|
35
|
-
light: studioTheme.color.light.primary.spot.blue,
|
|
36
|
-
},
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
type SchemeColorKey = keyof typeof SCHEME_COLORS
|
|
40
|
-
|
|
41
|
-
export function getSchemeColor(scheme: ThemeColorSchemeKey, colorKey: SchemeColorKey): string {
|
|
42
|
-
return SCHEME_COLORS[colorKey]?.[scheme]
|
|
43
|
-
}
|
|
@@ -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
|
-
}
|