@electrolux-oss/sanity-plugin-media 4.0.0 → 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/README.md CHANGED
@@ -1,28 +1,14 @@
1
- # Sanity Media Plugin - Electrolux Version (for Sanity Studio v5 and v6)
1
+ # Sanity Media Plugin - Electrolux Version (for Sanity Studio v6)
2
2
 
3
- > This plugin supports **Sanity Studio v5 and v6**.
4
- > This is a forked version of Sanitys own [Media Plugin (v4.0.0)](https://github.com/sanity-io/sanity-plugin-media)
3
+ > This plugin supports **Sanity Studio v6.20 and newer**.
4
+ > Originally based on Sanity's [sanity-io/sanity-plugin-media](https://github.com/sanity-io/sanity-plugin-media) (v4.0.0, MIT).
5
5
 
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
-
14
- ### **v2 → v3**
15
-
16
- This release upgrades the plugin’s peer dependency from **React 18** to **React 19**.
17
- Projects using this plugin must update to React 19 before upgrading to v3.
18
-
19
- ## What is it?
20
-
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.
6
+ > [!WARNING]
7
+ > Breaking changes introduced in version 5.0.0 of this plugin. See [Breaking changes](#breaking-changes).
22
8
 
23
9
  A convenient way to browse, manage and refine your [Sanity](https://www.sanity.io/) assets.
24
10
 
25
- Use it standalone as a browser, or optionally hook it up as a [custom asset source](https://www.sanity.io/docs/custom-asset-sources) and use it to power both image and file selection too.
11
+ Use it standalone as a browser, or hook it up as a [custom asset source](https://www.sanity.io/docs/custom-asset-sources) to power both image and file selection too.
26
12
 
27
13
  ![Grid view](https://user-images.githubusercontent.com/209129/108927411-21aa7f00-7638-11eb-9cf7-334598ac4103.png)
28
14
  _Default grid view_
@@ -58,14 +44,24 @@ _Default grid view_
58
44
  - Built with the same [UI components Sanity uses](https://www.sanity.io/ui) under the hood
59
45
  - Fully responsive and mobile friendly
60
46
 
61
- #### Added support for multi-language alt texts
47
+ #### Electrolux additions
62
48
 
63
- - Define a list of available languages to enable multi-language alt texts for assets.
64
- - See example of how to enable this feature [here](###plugin-config).
49
+ - Multi-language alt texts, with validation of the default language (see [Plugin config](#plugin-config))
50
+ - Automatic tagging of assets selected or uploaded through a field (see [Auto-tagging assets from a field](#auto-tagging-assets-from-a-field))
51
+ - An **Edit Media** option on image and file fields (see [Edit Media](#edit-media))
52
+ - A maximum upload size for every upload in the Studio, and tags that hide assets from the browser
53
+ - A record of who uploaded each asset (see [Uploaded by](#uploaded-by))
65
54
 
66
- ## Install (Sanity Studio v5 and v6)
55
+ ## Install (Sanity Studio v6.20+)
67
56
 
68
- In your Sanity project folder:
57
+ | Package / Runtime | Requirement |
58
+ | --------------------- | ----------- |
59
+ | `sanity` | `^6.20.0` |
60
+ | `react` / `react-dom` | `^19.2.2` |
61
+ | `styled-components` | `^6.1.15` |
62
+ | Node.js | `>=22.12` |
63
+
64
+ Run
69
65
 
70
66
  ```zsh
71
67
  npm install --save @electrolux-oss/sanity-plugin-media
@@ -115,14 +111,16 @@ export default defineConfig({
115
111
  // Don't use this plugin when selecting files only (but allow all other enabled asset sources)
116
112
  file: {
117
113
  assetSources: previousAssetSources => {
118
- return previousAssetSources.filter(assetSource => assetSource !== mediaAssetSource)
114
+ return previousAssetSources.filter(
115
+ assetSource => assetSource.name !== mediaAssetSource.name
116
+ )
119
117
  }
120
118
  }
121
119
  }
122
120
  })
123
121
  ```
124
122
 
125
- ### Plugin Config
123
+ ### Plugin config
126
124
 
127
125
  ```ts
128
126
  // sanity.config.ts
@@ -147,9 +145,20 @@ export default defineConfig({
147
145
  // wish to prevent users overwriting the creditLine based on the `source.name`
148
146
  },
149
147
  maximumUploadSize: 10000000,
150
- // number - maximum file size (in bytes) that can be uploaded through the plugin interface
148
+ // number - maximum file size (in bytes) for uploads, both through the plugin interface and
149
+ // through the image / file fields' own upload button (any upload-capable asset source)
151
150
  directUploads: true,
152
151
  // boolean - enable / disable direct uploads through the plugin interface (default true)
152
+ createTagsOnUpload: true,
153
+ // boolean - create tags listed in a field's `mediaTags` if they don't exist yet (default true)
154
+ uploadMenu: false,
155
+ // boolean - offer "Media" as an upload target in image / file fields (default true).
156
+ // Set to false to make the upload button use the workspace's own uploader directly;
157
+ // "Media" is then only listed under "Select"
158
+ excludeTags: ['internal', 'archived'],
159
+ // string[] (optional) - tag slugs (`media.tag` `name.current`). Assets with any of these
160
+ // tags are hidden from the Media browser and asset picker, and the tags themselves are
161
+ // hidden in the tag panel and tag search facet. The asset edit dialog still lists all tags.
153
162
  components: {
154
163
  details: CustomDetails
155
164
  }
@@ -158,9 +167,61 @@ export default defineConfig({
158
167
  })
159
168
  ```
160
169
 
170
+ ### Auto-tagging assets from a field
171
+
172
+ 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.
173
+
174
+ ```ts
175
+ import { mediaField } from '@electrolux-oss/sanity-plugin-media'
176
+
177
+ defineType({
178
+ name: 'product',
179
+ type: 'document',
180
+ fields: [
181
+ mediaField({
182
+ name: 'image',
183
+ type: 'image',
184
+ mediaTags: ['product']
185
+ })
186
+ ]
187
+ })
188
+ ```
189
+
190
+ Or use the input component directly:
191
+
192
+ ```tsx
193
+ import { AutoTagInput } from '@electrolux-oss/sanity-plugin-media'
194
+
195
+ defineField({
196
+ name: 'image',
197
+ type: 'image',
198
+ options: { mediaTags: ['product'] }, // pre-filters the browser
199
+ components: { input: AutoTagInput } // applies tags on select
200
+ })
201
+
202
+ // Or pass mediaTags as a prop to override the field options:
203
+ defineField({
204
+ name: 'image',
205
+ type: 'image',
206
+ components: {
207
+ input: props => <AutoTagInput {...props} mediaTags={['product']} />
208
+ }
209
+ })
210
+ ```
211
+
212
+ 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`.
213
+
214
+ ### Uploaded by
215
+
216
+ 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**.
217
+
218
+ ### Edit Media
219
+
220
+ 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.
221
+
161
222
  ## Develop locally
162
223
 
163
- ### Test your plugin locally
224
+ ### Link the plugin to a local studio
164
225
 
165
226
  **Step 1: Set up the plugin for local development**
166
227
 
@@ -199,8 +260,57 @@ import { media } from '@electrolux-oss/sanity-plugin-media'
199
260
 
200
261
  ### Making changes
201
262
 
202
- When you modify the plugin code:
263
+ When you modify the plugin code, `link-watch` rebuilds it and pushes the build to every studio
264
+ linked with yalc. Reload the studio to pick up the changes, and restart its dev server if it still
265
+ serves the old build.
266
+
267
+ ### Running the checks
268
+
269
+ ```zsh
270
+ npm run typecheck # tsc --noEmit
271
+ npm run lint # ESLint
272
+ npm run build # also fails on type errors
273
+ ```
274
+
275
+ This plugin has no automated tests yet.
276
+
277
+ ## Breaking changes
278
+
279
+ ### **v4 → v5**
280
+
281
+ This release requires **Sanity Studio v6.20 or newer**, the first Studio release on `@sanity/ui`
282
+ ^4.4.9. Support for Studio v5 and Studio v6 before 6.20 has been dropped; stay on v4 of this
283
+ plugin for older Studios.
203
284
 
204
- 1. The `link-watch` command will automatically rebuild
205
- 2. In your studio, run `npx yalc update` to pull the latest changes
206
- 3. Restart your dev server: `npm run dev`
285
+ - The plugin now uses the same `@sanity/ui` (v4) and `@sanity/client` (v8) versions as the Studio,
286
+ so npm installs one shared copy of each instead of a second one inside the plugin.
287
+ - `react-is` is no longer a peer dependency.
288
+ - The tag inputs take their colors and fonts from the Studio's theme, so they follow custom themes.
289
+
290
+ Requirements:
291
+
292
+ | Package / Runtime | Requirement |
293
+ | --------------------- | ----------- |
294
+ | `sanity` | `^6.20.0` |
295
+ | `react` / `react-dom` | `^19.2.2` |
296
+ | `styled-components` | `^6.1.15` |
297
+ | Node.js | `>=22.12` |
298
+
299
+ ### **v3 → v4**
300
+
301
+ This release targets **Sanity Studio v5 and v6**. Support for Studio v4 has been dropped, and the
302
+ package is now **ESM-only** (the CommonJS build has been removed).
303
+
304
+ Requirements:
305
+
306
+ | Package / Runtime | Requirement |
307
+ | ---------------------------------- | -------------------- |
308
+ | `sanity` | `^5.0.0 \|\| ^6.0.0` |
309
+ | `react` / `react-dom` / `react-is` | `^19.2.2` |
310
+ | `styled-components` | `^6.1.15` |
311
+ | Node.js | `>=22.12` |
312
+
313
+ ### **v2 → v3**
314
+
315
+ This release upgrades the plugin’s peer dependency from **React 18** to **React 19**.
316
+ Projects using this plugin must update to React 19 before upgrading to v3.
package/dist/index.d.ts CHANGED
@@ -1,8 +1,12 @@
1
1
  import { AssetSourceComponentProps } from 'sanity'
2
2
  import type { ComponentType } from 'react'
3
3
  import type { Control } from 'react-hook-form'
4
+ import type { FieldDefinitionBase } from 'sanity'
4
5
  import type { FieldErrors } from 'react-hook-form'
6
+ import type { FileDefinition } from 'sanity'
5
7
  import { ForwardRefExoticComponent } from 'react'
8
+ import type { ImageDefinition } from 'sanity'
9
+ import type { InputProps } from 'sanity'
6
10
  import { JSX } from 'react'
7
11
  import { Plugin as Plugin_2 } from 'sanity'
8
12
  import { RefAttributes } from 'react'
@@ -10,6 +14,8 @@ import type { SanityAssetDocument } from '@sanity/client'
10
14
  import type { SanityImageAssetDocument } from '@sanity/client'
11
15
  import { SVGProps } from 'react'
12
16
  import type { UseFormRegister } from 'react-hook-form'
17
+ import type { WidenInitialValue } from 'sanity'
18
+ import type { WidenValidation } from 'sanity'
13
19
  import * as z from 'zod'
14
20
 
15
21
  declare type Asset = FileAsset | ImageAsset
@@ -18,19 +24,26 @@ declare type AssetFormData = z.infer<AssetFormSchemaObject>
18
24
 
19
25
  declare type AssetFormSchemaObject = ReturnType<typeof createAssetFormSchema>
20
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
+
21
40
  declare const createAssetFormSchema: (
22
41
  languages: MediaToolOptions['languages'],
23
42
  validateAltTexts?: boolean
24
43
  ) => z.ZodObject<
25
44
  {
26
45
  altText: z.ZodOptional<z.ZodString>
27
- altTexts:
28
- | z.ZodRecord<z.ZodString, z.ZodNullable<z.ZodString>>
29
- | z.ZodEffects<
30
- z.ZodRecord<z.ZodString, z.ZodNullable<z.ZodString>>,
31
- Record<string, string | null>,
32
- Record<string, string | null>
33
- >
46
+ altTexts: z.ZodRecord<z.ZodString, z.ZodNullable<z.ZodString>>
34
47
  creditLine: z.ZodOptional<z.ZodString>
35
48
  description: z.ZodOptional<z.ZodString>
36
49
  opt: z.ZodObject<
@@ -44,105 +57,20 @@ declare const createAssetFormSchema: (
44
57
  label: z.ZodString
45
58
  value: z.ZodString
46
59
  },
47
- 'strip',
48
- z.ZodTypeAny,
49
- {
50
- label: string
51
- value: string
52
- },
53
- {
54
- label: string
55
- value: string
56
- }
57
- >,
58
- 'many'
60
+ z.core.$strip
61
+ >
59
62
  >
60
63
  >
61
64
  },
62
- 'strip',
63
- z.ZodTypeAny,
64
- {
65
- tags:
66
- | {
67
- label: string
68
- value: string
69
- }[]
70
- | null
71
- },
72
- {
73
- tags:
74
- | {
75
- label: string
76
- value: string
77
- }[]
78
- | null
79
- }
65
+ z.core.$strip
80
66
  >
81
67
  },
82
- 'strip',
83
- z.ZodTypeAny,
84
- {
85
- media: {
86
- tags:
87
- | {
88
- label: string
89
- value: string
90
- }[]
91
- | null
92
- }
93
- },
94
- {
95
- media: {
96
- tags:
97
- | {
98
- label: string
99
- value: string
100
- }[]
101
- | null
102
- }
103
- }
68
+ z.core.$strip
104
69
  >
105
70
  originalFilename: z.ZodString
106
71
  title: z.ZodOptional<z.ZodString>
107
72
  },
108
- 'strip',
109
- z.ZodTypeAny,
110
- {
111
- altTexts: Record<string, string | null>
112
- opt: {
113
- media: {
114
- tags:
115
- | {
116
- label: string
117
- value: string
118
- }[]
119
- | null
120
- }
121
- }
122
- originalFilename: string
123
- altText?: string | undefined
124
- creditLine?: string | undefined
125
- description?: string | undefined
126
- title?: string | undefined
127
- },
128
- {
129
- altTexts: Record<string, string | null>
130
- opt: {
131
- media: {
132
- tags:
133
- | {
134
- label: string
135
- value: string
136
- }[]
137
- | null
138
- }
139
- }
140
- originalFilename: string
141
- altText?: string | undefined
142
- creditLine?: string | undefined
143
- description?: string | undefined
144
- title?: string | undefined
145
- }
73
+ z.core.$strip
146
74
  >
147
75
 
148
76
  declare type CustomFields = {
@@ -152,6 +80,7 @@ declare type CustomFields = {
152
80
  opt?: {
153
81
  media?: {
154
82
  tags?: SanityReference[]
83
+ uploadedBy?: Uploader | string
155
84
  }
156
85
  }
157
86
  title?: string
@@ -195,6 +124,42 @@ export declare const mediaAssetSource: {
195
124
  title: string
196
125
  }
197
126
 
127
+ /**
128
+ * Defines an image or file field whose selected / uploaded assets are tagged with `mediaTags`.
129
+ *
130
+ * `mediaTags` is copied to `options.mediaTags` (which pre-filters the media browser) and
131
+ * {@link AutoTagInput} is set as the input component.
132
+ *
133
+ * ```ts
134
+ * mediaField({ name: 'image', type: 'image', mediaTags: ['product'] })
135
+ * ```
136
+ */
137
+ export declare function mediaField(
138
+ config: MediaFieldConfig<ImageDefinition>
139
+ ): MediaFieldResult<ImageDefinition>
140
+
141
+ export declare function mediaField(
142
+ config: MediaFieldConfig<FileDefinition>
143
+ ): MediaFieldResult<FileDefinition>
144
+
145
+ declare type MediaFieldConfig<T extends ImageDefinition | FileDefinition> = Omit<T, 'options'> &
146
+ FieldDefinitionBase & {
147
+ name: string
148
+ mediaTags: string[]
149
+ options?: T['options']
150
+ }
151
+
152
+ declare type MediaFieldResult<T extends ImageDefinition | FileDefinition> = Omit<T, 'options'> &
153
+ FieldDefinitionBase & {
154
+ options?: T['options'] & {
155
+ mediaTags: string[]
156
+ }
157
+ components: {
158
+ input: typeof AutoTagInput
159
+ }
160
+ } & WidenValidation &
161
+ WidenInitialValue
162
+
198
163
  export declare type MediaToolOptions = {
199
164
  languages: {
200
165
  code: string
@@ -202,6 +167,7 @@ export declare type MediaToolOptions = {
202
167
  default?: boolean
203
168
  }[]
204
169
  maximumUploadSize?: number
170
+ createTagsOnUpload?: boolean
205
171
  components?: {
206
172
  details?: ComponentType<
207
173
  DetailsProps & {
@@ -214,6 +180,13 @@ export declare type MediaToolOptions = {
214
180
  excludeSources?: string | string[]
215
181
  }
216
182
  directUploads?: boolean
183
+ uploadMenu?: boolean
184
+ /**
185
+ * Tag slugs (`media.tag` `name.current`) whose assets are hidden from the Media browser and
186
+ * asset picker, along with the tags themselves in the tag panel and tag search facet.
187
+ * The asset edit dialog still lists every tag.
188
+ */
189
+ excludeTags?: string[]
217
190
  }
218
191
 
219
192
  declare type SanityReference = {
@@ -227,18 +200,22 @@ declare const tagOptionSchema: z.ZodObject<
227
200
  label: z.ZodString
228
201
  value: z.ZodString
229
202
  },
230
- 'strip',
231
- z.ZodTypeAny,
232
- {
233
- label: string
234
- value: string
235
- },
236
- {
237
- label: string
238
- value: string
239
- }
203
+ z.core.$strip
240
204
  >
241
205
 
242
206
  declare type TagSelectOption = z.infer<typeof tagOptionSchema>
243
207
 
208
+ declare type Uploader = {
209
+ id: string
210
+ /** Only set by earlier builds; the name is now looked up on demand. */
211
+ name?: string
212
+ }
213
+
244
214
  export {}
215
+
216
+ declare module 'sanity' {
217
+ interface FileOptions {
218
+ /** Tags applied by `AutoTagInput` / `mediaField`, also used to pre-filter the media browser. */
219
+ mediaTags?: string[]
220
+ }
221
+ }