@electrolux-oss/sanity-plugin-media 3.0.0 → 4.1.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/README.md CHANGED
@@ -1,9 +1,16 @@
1
- # Sanity Media Plugin - Electrolux Version (for Sanity Studio v4)
1
+ # Sanity Media Plugin - Electrolux Version (for Sanity Studio v5 and v6)
2
2
 
3
- > This plugin is for **Sanity Studio v4**.
3
+ > This plugin supports **Sanity Studio v5 and v6**.
4
4
  > This is a forked version of Sanitys own [Media Plugin (v4.0.0)](https://github.com/sanity-io/sanity-plugin-media)
5
5
 
6
- ## :warning: Breaking changes - (v2.0.0 → v3.0.0)
6
+ ## :warning: Breaking changes - (v3.0.0 → v4.0.0)
7
+
8
+ ### **v3 → v4**
9
+
10
+ This release targets **Sanity Studio v5 and v6**. Support for Studio v4 has been dropped, and the
11
+ package is now **ESM-only** (the CommonJS build has been removed).
12
+ Projects must be on Studio v5 or v6 (and React 19.2.2 or newer) before upgrading to v4.
13
+
7
14
  ### **v2 → v3**
8
15
 
9
16
  This release upgrades the plugin’s peer dependency from **React 18** to **React 19**.
@@ -11,7 +18,7 @@ Projects using this plugin must update to React 19 before upgrading to v3.
11
18
 
12
19
  ## What is it?
13
20
 
14
- A Sanity Studio v4 plugin that extends media handling capabilities with custom enhancements, based on a fork of an original plugin by Sanity.io.
21
+ A Sanity Studio v5 / v6 plugin that extends media handling capabilities with custom enhancements, based on a fork of an original plugin by Sanity.io.
15
22
 
16
23
  A convenient way to browse, manage and refine your [Sanity](https://www.sanity.io/) assets.
17
24
 
@@ -20,11 +27,9 @@ Use it standalone as a browser, or optionally hook it up as a [custom asset sour
20
27
  ![Grid view](https://user-images.githubusercontent.com/209129/108927411-21aa7f00-7638-11eb-9cf7-334598ac4103.png)
21
28
  _Default grid view_
22
29
 
23
-
24
- _Asset details tab_ | _Asset alt texts tab_
25
- :-------------------------:|:-------------------------:
26
- ![first](https://github.com/user-attachments/assets/477b6576-a1a4-42e2-b2c2-2d2e962eb366) | ![second](https://github.com/user-attachments/assets/2c5bd907-1b60-48c6-a007-2f7304409007)
27
-
30
+ | _Asset details tab_ | _Asset alt texts tab_ |
31
+ | :---------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------: |
32
+ | ![first](https://github.com/user-attachments/assets/477b6576-a1a4-42e2-b2c2-2d2e962eb366) | ![second](https://github.com/user-attachments/assets/2c5bd907-1b60-48c6-a007-2f7304409007) |
28
33
 
29
34
  ## Features
30
35
 
@@ -58,18 +63,18 @@ _Asset details tab_ | _Asset alt texts tab_
58
63
  - Define a list of available languages to enable multi-language alt texts for assets.
59
64
  - See example of how to enable this feature [here](###plugin-config).
60
65
 
61
- ## Install (Sanity Studio v4)
66
+ ## Install (Sanity Studio v5 and v6)
62
67
 
63
68
  In your Sanity project folder:
64
69
 
65
70
  ```zsh
66
- npm install --save sanity-plugin-media
71
+ npm install --save @electrolux-oss/sanity-plugin-media
67
72
  ```
68
73
 
69
74
  or
70
75
 
71
76
  ```zsh
72
- yarn add sanity-plugin-media
77
+ yarn add @electrolux-oss/sanity-plugin-media
73
78
  ```
74
79
 
75
80
  ## Usage
@@ -77,19 +82,19 @@ yarn add sanity-plugin-media
77
82
  Add it as a plugin in your `sanity.config.ts` (or .js) file:
78
83
 
79
84
  ```js
80
- import { media } from 'sanity-plugin-media'
85
+ import { media } from '@electrolux-oss/sanity-plugin-media'
81
86
 
82
87
  export default defineConfig({
83
88
  // ...
84
89
  plugins: [
85
- media(
90
+ media({
86
91
  languages: [
87
92
  { code: 'en-US', title: 'English', default: true },
88
93
  { code: 'sv-SE', title: 'Swedish' },
89
94
  { code: 'fr-FR', title: 'French' },
90
95
  { code: 'de-DE', title: 'German' }
91
- ],
92
- )
96
+ ]
97
+ })
93
98
  ]
94
99
  })
95
100
  ```
@@ -101,7 +106,7 @@ This will enable the Media plugin as both a standalone tool (accessible in your
101
106
  You can configure your studio to use this asset source either exclusively, or conditionally enable it based on the type of asset (image or file).
102
107
 
103
108
  ```js
104
- import { media, mediaAssetSource } from 'sanity-plugin-media'
109
+ import { media, mediaAssetSource } from '@electrolux-oss/sanity-plugin-media'
105
110
 
106
111
  export default defineConfig({
107
112
  // ...
@@ -110,7 +115,9 @@ export default defineConfig({
110
115
  // Don't use this plugin when selecting files only (but allow all other enabled asset sources)
111
116
  file: {
112
117
  assetSources: previousAssetSources => {
113
- return previousAssetSources.filter(assetSource => assetSource !== mediaAssetSource)
118
+ return previousAssetSources.filter(
119
+ assetSource => assetSource.name !== mediaAssetSource.name
120
+ )
114
121
  }
115
122
  }
116
123
  }
@@ -126,7 +133,7 @@ export default defineConfig({
126
133
  plugins: [
127
134
  media({
128
135
  // { code: string; title: string, default?: boolean }[] - the list of languages used for alt texts
129
- // NOTE! Set only one language to default: true.
136
+ // NOTE! Set only one language to default: true.
130
137
  languages: [
131
138
  { code: 'en-US', title: 'English', default: true },
132
139
  { code: 'sv-SE', title: 'Swedish' },
@@ -137,17 +144,85 @@ export default defineConfig({
137
144
  enabled: true,
138
145
  // boolean - enables an optional "Credit Line" field in the plugin.
139
146
  // Used to store credits e.g. photographer, licence information
140
- excludeSources: ['unsplash'],
147
+ excludeSources: ['unsplash']
141
148
  // string | string[] - when used with 3rd party asset sources, you may
142
149
  // wish to prevent users overwriting the creditLine based on the `source.name`
143
150
  },
144
- maximumUploadSize: 10000000
145
- // number - maximum file size (in bytes) that can be uploaded through the plugin interface
151
+ maximumUploadSize: 10000000,
152
+ // number - maximum file size (in bytes) for uploads, both through the plugin interface and
153
+ // through the image / file fields' own upload button (any upload-capable asset source)
154
+ directUploads: true,
155
+ // boolean - enable / disable direct uploads through the plugin interface (default true)
156
+ createTagsOnUpload: true,
157
+ // boolean - create tags listed in a field's `mediaTags` if they don't exist yet (default true)
158
+ uploadMenu: false,
159
+ // boolean - offer "Media" as an upload target in image / file fields (default true).
160
+ // Set to false to make the upload button use the workspace's own uploader directly;
161
+ // "Media" is then only listed under "Select"
162
+ excludeTags: ['internal', 'archived'],
163
+ // string[] (optional) - tag slugs (`media.tag` `name.current`). Assets with any of these
164
+ // tags are hidden from the Media browser and asset picker, and the tags themselves are
165
+ // hidden in the tag panel and tag search facet. The asset edit dialog still lists all tags.
166
+ components: {
167
+ details: CustomDetails
168
+ }
146
169
  })
147
- ],
170
+ ]
148
171
  })
149
172
  ```
150
173
 
174
+ ### Auto-tagging assets from a field
175
+
176
+ Assets selected or uploaded through a field can be tagged automatically. The same tags pre-filter the media browser when it is opened from that field.
177
+
178
+ ```ts
179
+ import { mediaField } from '@electrolux-oss/sanity-plugin-media'
180
+
181
+ defineType({
182
+ name: 'product',
183
+ type: 'document',
184
+ fields: [
185
+ mediaField({
186
+ name: 'image',
187
+ type: 'image',
188
+ mediaTags: ['product']
189
+ })
190
+ ]
191
+ })
192
+ ```
193
+
194
+ Or use the input component directly:
195
+
196
+ ```tsx
197
+ import { AutoTagInput } from '@electrolux-oss/sanity-plugin-media'
198
+
199
+ defineField({
200
+ name: 'image',
201
+ type: 'image',
202
+ options: { mediaTags: ['product'] }, // pre-filters the browser
203
+ components: { input: AutoTagInput } // applies tags on select
204
+ })
205
+
206
+ // Or pass mediaTags as a prop to override the field options:
207
+ defineField({
208
+ name: 'image',
209
+ type: 'image',
210
+ components: {
211
+ input: props => <AutoTagInput {...props} mediaTags={['product']} />
212
+ }
213
+ })
214
+ ```
215
+
216
+ Only assets selected after the document is opened are tagged. Existing values are left untouched. Tags missing from the dataset are created unless `createTagsOnUpload` is `false`.
217
+
218
+ ### Uploaded by
219
+
220
+ The asset details panel shows who uploaded an asset. It is recorded as `opt.media.uploadedBy` (`{ id }`, the Sanity user ID only; the sync button next to it fetches the person's name, and email when Sanity returns it, on demand) when a logged-in user uploads through the Studio: in the Media browser, or with an image / file field's own upload button and drag and drop. Only uploads made in that user's browser are recorded, so collaborators editing the same document are never credited. Assets uploaded by scripts, the CLI, API tokens or before this feature existed show **Unknown**.
221
+
222
+ ### Edit Media
223
+
224
+ The plugin adds an **Edit Media** option to the asset menu of image and file fields. It opens the edit dialog (title, alt texts, description, tags, …) for the field's current asset, without leaving the document.
225
+
151
226
  ## Develop locally
152
227
 
153
228
  ### Test your plugin locally
@@ -155,17 +230,20 @@ export default defineConfig({
155
230
  **Step 1: Set up the plugin for local development**
156
231
 
157
232
  In the plugin directory, run:
233
+
158
234
  ```zsh
159
235
  npm run link-watch
160
236
  ```
161
237
 
162
238
  This will:
239
+
163
240
  - Build your plugin automatically whenever code changes
164
241
  - Publish the package to a local yalc repository
165
242
 
166
243
  **Step 2: Link the plugin to your studio**
167
244
 
168
245
  In your studio project directory, run:
246
+
169
247
  ```zsh
170
248
  npx yalc add @electrolux-oss/sanity-plugin-media && npm install
171
249
  ```
@@ -173,18 +251,21 @@ npx yalc add @electrolux-oss/sanity-plugin-media && npm install
173
251
  **Step 3: Verify the setup**
174
252
 
175
253
  Check your studio's `package.json` - you should see:
254
+
176
255
  ```json
177
256
  "@electrolux-oss/sanity-plugin-media": "file:.yalc/@electrolux-oss/sanity-plugin-media"
178
257
  ```
179
258
 
180
259
  Now you can use the local version of the plugin:
260
+
181
261
  ```typescript
182
- import { workflow } from '@electrolux-oss/sanity-plugin-media'
262
+ import { media } from '@electrolux-oss/sanity-plugin-media'
183
263
  ```
184
264
 
185
265
  ### Making changes
186
266
 
187
267
  When you modify the plugin code:
268
+
188
269
  1. The `link-watch` command will automatically rebuild
189
270
  2. In your studio, run `npx yalc update` to pull the latest changes
190
271
  3. Restart your dev server: `npm run dev`
package/dist/index.d.ts CHANGED
@@ -1,14 +1,213 @@
1
1
  import { AssetSourceComponentProps } from 'sanity'
2
+ import type { ComponentType } from 'react'
3
+ import type { Control } from 'react-hook-form'
4
+ import type { FieldDefinitionBase } from 'sanity'
5
+ import type { FieldErrors } from 'react-hook-form'
6
+ import type { FileDefinition } from 'sanity'
2
7
  import { ForwardRefExoticComponent } from 'react'
8
+ import type { ImageDefinition } from 'sanity'
9
+ import type { InputProps } from 'sanity'
3
10
  import { JSX } from 'react'
4
11
  import { Plugin as Plugin_2 } from 'sanity'
5
12
  import { RefAttributes } from 'react'
13
+ import type { SanityAssetDocument } from '@sanity/client'
14
+ import type { SanityImageAssetDocument } from '@sanity/client'
6
15
  import { SVGProps } from 'react'
16
+ import type { UseFormRegister } from 'react-hook-form'
17
+ import type { WidenInitialValue } from 'sanity'
18
+ import type { WidenValidation } from 'sanity'
19
+ import * as z from 'zod'
20
+
21
+ declare type Asset = FileAsset | ImageAsset
22
+
23
+ declare type AssetFormData = z.infer<AssetFormSchemaObject>
24
+
25
+ declare type AssetFormSchemaObject = ReturnType<typeof createAssetFormSchema>
26
+
27
+ /**
28
+ * Image / file input that tags the selected or uploaded asset with `mediaTags`.
29
+ *
30
+ * Tags come from the `mediaTags` prop, falling back to `options.mediaTags` on the field
31
+ * (which also pre-filters the media browser). Only changes made after mount are tagged,
32
+ * so opening a document never patches its existing assets.
33
+ */
34
+ export declare function AutoTagInput(props: AutoTagInputProps): JSX
35
+
36
+ export declare type AutoTagInputProps = InputProps & {
37
+ mediaTags?: string[]
38
+ }
39
+
40
+ declare const createAssetFormSchema: (
41
+ languages: MediaToolOptions['languages'],
42
+ validateAltTexts?: boolean
43
+ ) => z.ZodObject<
44
+ {
45
+ altText: z.ZodOptional<z.ZodString>
46
+ altTexts:
47
+ | z.ZodRecord<z.ZodString, z.ZodNullable<z.ZodString>>
48
+ | z.ZodEffects<
49
+ z.ZodRecord<z.ZodString, z.ZodNullable<z.ZodString>>,
50
+ Record<string, string | null>,
51
+ Record<string, string | null>
52
+ >
53
+ creditLine: z.ZodOptional<z.ZodString>
54
+ description: z.ZodOptional<z.ZodString>
55
+ opt: z.ZodObject<
56
+ {
57
+ media: z.ZodObject<
58
+ {
59
+ tags: z.ZodNullable<
60
+ z.ZodArray<
61
+ z.ZodObject<
62
+ {
63
+ label: z.ZodString
64
+ value: z.ZodString
65
+ },
66
+ 'strip',
67
+ z.ZodTypeAny,
68
+ {
69
+ label: string
70
+ value: string
71
+ },
72
+ {
73
+ label: string
74
+ value: string
75
+ }
76
+ >,
77
+ 'many'
78
+ >
79
+ >
80
+ },
81
+ 'strip',
82
+ z.ZodTypeAny,
83
+ {
84
+ tags:
85
+ | {
86
+ label: string
87
+ value: string
88
+ }[]
89
+ | null
90
+ },
91
+ {
92
+ tags:
93
+ | {
94
+ label: string
95
+ value: string
96
+ }[]
97
+ | null
98
+ }
99
+ >
100
+ },
101
+ 'strip',
102
+ z.ZodTypeAny,
103
+ {
104
+ media: {
105
+ tags:
106
+ | {
107
+ label: string
108
+ value: string
109
+ }[]
110
+ | null
111
+ }
112
+ },
113
+ {
114
+ media: {
115
+ tags:
116
+ | {
117
+ label: string
118
+ value: string
119
+ }[]
120
+ | null
121
+ }
122
+ }
123
+ >
124
+ originalFilename: z.ZodString
125
+ title: z.ZodOptional<z.ZodString>
126
+ },
127
+ 'strip',
128
+ z.ZodTypeAny,
129
+ {
130
+ altTexts: Record<string, string | null>
131
+ opt: {
132
+ media: {
133
+ tags:
134
+ | {
135
+ label: string
136
+ value: string
137
+ }[]
138
+ | null
139
+ }
140
+ }
141
+ originalFilename: string
142
+ altText?: string | undefined
143
+ creditLine?: string | undefined
144
+ description?: string | undefined
145
+ title?: string | undefined
146
+ },
147
+ {
148
+ altTexts: Record<string, string | null>
149
+ opt: {
150
+ media: {
151
+ tags:
152
+ | {
153
+ label: string
154
+ value: string
155
+ }[]
156
+ | null
157
+ }
158
+ }
159
+ originalFilename: string
160
+ altText?: string | undefined
161
+ creditLine?: string | undefined
162
+ description?: string | undefined
163
+ title?: string | undefined
164
+ }
165
+ >
166
+
167
+ declare type CustomFields = {
168
+ altText?: string
169
+ altTexts?: Record<string, string>
170
+ description?: string
171
+ opt?: {
172
+ media?: {
173
+ tags?: SanityReference[]
174
+ uploadedBy?: Uploader | string
175
+ }
176
+ }
177
+ title?: string
178
+ }
179
+
180
+ declare type DetailsProps = {
181
+ formUpdating: boolean
182
+ handleCreateTag: (title: string) => void
183
+ control: Control<AssetFormData>
184
+ errors: FieldErrors<AssetFormData>
185
+ register: UseFormRegister<AssetFormData>
186
+ allTagOptions: TagSelectOption[]
187
+ assetTagOptions: TagSelectOption[] | null
188
+ currentAsset: Asset
189
+ creditLine?: {
190
+ enabled: boolean
191
+ excludeSources?: string | string[] | undefined
192
+ }
193
+ }
194
+
195
+ declare type FileAsset = SanityAssetDocument &
196
+ CustomFields & {
197
+ _type: 'sanity.fileAsset'
198
+ }
199
+
200
+ declare type ImageAsset = SanityImageAssetDocument &
201
+ CustomFields & {
202
+ _type: 'sanity.imageAsset'
203
+ creditLine?: string
204
+ }
7
205
 
8
206
  export declare const media: Plugin_2<void | MediaToolOptions>
9
207
 
10
208
  export declare const mediaAssetSource: {
11
- component: (props: AssetSourceComponentProps) => JSX.Element
209
+ component: (props: AssetSourceComponentProps) => JSX
210
+ uploadMode: 'component'
12
211
  icon: ForwardRefExoticComponent<
13
212
  Omit<SVGProps<SVGSVGElement>, 'ref'> & RefAttributes<SVGSVGElement>
14
213
  >
@@ -16,6 +215,42 @@ export declare const mediaAssetSource: {
16
215
  title: string
17
216
  }
18
217
 
218
+ /**
219
+ * Defines an image or file field whose selected / uploaded assets are tagged with `mediaTags`.
220
+ *
221
+ * `mediaTags` is copied to `options.mediaTags` (which pre-filters the media browser) and
222
+ * {@link AutoTagInput} is set as the input component.
223
+ *
224
+ * ```ts
225
+ * mediaField({ name: 'image', type: 'image', mediaTags: ['product'] })
226
+ * ```
227
+ */
228
+ export declare function mediaField(
229
+ config: MediaFieldConfig<ImageDefinition>
230
+ ): MediaFieldResult<ImageDefinition>
231
+
232
+ export declare function mediaField(
233
+ config: MediaFieldConfig<FileDefinition>
234
+ ): MediaFieldResult<FileDefinition>
235
+
236
+ declare type MediaFieldConfig<T extends ImageDefinition | FileDefinition> = Omit<T, 'options'> &
237
+ FieldDefinitionBase & {
238
+ name: string
239
+ mediaTags: string[]
240
+ options?: T['options']
241
+ }
242
+
243
+ declare type MediaFieldResult<T extends ImageDefinition | FileDefinition> = Omit<T, 'options'> &
244
+ FieldDefinitionBase & {
245
+ options?: T['options'] & {
246
+ mediaTags: string[]
247
+ }
248
+ components: {
249
+ input: typeof AutoTagInput
250
+ }
251
+ } & WidenValidation &
252
+ WidenInitialValue
253
+
19
254
  export declare type MediaToolOptions = {
20
255
  languages: {
21
256
  code: string
@@ -23,10 +258,64 @@ export declare type MediaToolOptions = {
23
258
  default?: boolean
24
259
  }[]
25
260
  maximumUploadSize?: number
261
+ createTagsOnUpload?: boolean
262
+ components?: {
263
+ details?: ComponentType<
264
+ DetailsProps & {
265
+ renderDefaultDetails: (props: DetailsProps) => JSX.Element
266
+ }
267
+ >
268
+ }
26
269
  creditLine: {
27
270
  enabled: boolean
28
271
  excludeSources?: string | string[]
29
272
  }
273
+ directUploads?: boolean
274
+ uploadMenu?: boolean
275
+ /**
276
+ * Tag slugs (`media.tag` `name.current`) whose assets are hidden from the Media browser and
277
+ * asset picker, along with the tags themselves in the tag panel and tag search facet.
278
+ * The asset edit dialog still lists every tag.
279
+ */
280
+ excludeTags?: string[]
281
+ }
282
+
283
+ declare type SanityReference = {
284
+ _ref: string
285
+ _type: 'reference'
286
+ _weak?: boolean
287
+ }
288
+
289
+ declare const tagOptionSchema: z.ZodObject<
290
+ {
291
+ label: z.ZodString
292
+ value: z.ZodString
293
+ },
294
+ 'strip',
295
+ z.ZodTypeAny,
296
+ {
297
+ label: string
298
+ value: string
299
+ },
300
+ {
301
+ label: string
302
+ value: string
303
+ }
304
+ >
305
+
306
+ declare type TagSelectOption = z.infer<typeof tagOptionSchema>
307
+
308
+ declare type Uploader = {
309
+ id: string
310
+ /** Only set by earlier builds; the name is now looked up on demand. */
311
+ name?: string
30
312
  }
31
313
 
32
314
  export {}
315
+
316
+ declare module 'sanity' {
317
+ interface FileOptions {
318
+ /** Tags applied by `AutoTagInput` / `mediaField`, also used to pre-filter the media browser. */
319
+ mediaTags?: string[]
320
+ }
321
+ }