@jhb.software/payload-alt-text-plugin 0.4.4 → 0.6.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 +108 -34
- package/dist/components/AltTextField.d.ts +1 -1
- package/dist/components/AltTextField.js +9 -0
- package/dist/components/AltTextField.js.map +1 -1
- package/dist/components/AltTextHealthWidget.js +10 -2
- package/dist/components/AltTextHealthWidget.js.map +1 -1
- package/dist/components/BulkGenerateAltTextsButton.js +3 -2
- package/dist/components/BulkGenerateAltTextsButton.js.map +1 -1
- package/dist/components/GenerateAltTextButton.js +3 -2
- package/dist/components/GenerateAltTextButton.js.map +1 -1
- package/dist/endpoints/bulkGenerateAltTexts.js +5 -0
- package/dist/endpoints/bulkGenerateAltTexts.js.map +1 -1
- package/dist/endpoints/generateAltText.js +9 -0
- package/dist/endpoints/generateAltText.js.map +1 -1
- package/dist/fields/altTextField.d.ts +7 -2
- package/dist/fields/altTextField.js +5 -19
- package/dist/fields/altTextField.js.map +1 -1
- package/dist/hooks/revalidateAltTextHealth.js +5 -0
- package/dist/hooks/revalidateAltTextHealth.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/plugin.js +23 -47
- package/dist/plugin.js.map +1 -1
- package/dist/types/AltTextPluginConfig.d.ts +21 -5
- package/dist/types/AltTextPluginConfig.js.map +1 -1
- package/dist/utilities/altTextHealth.js +18 -10
- package/dist/utilities/altTextHealth.js.map +1 -1
- package/dist/utilities/mimeTypes.d.ts +70 -0
- package/dist/utilities/mimeTypes.js +115 -0
- package/dist/utilities/mimeTypes.js.map +1 -0
- package/package.json +17 -15
package/README.md
CHANGED
|
@@ -12,20 +12,17 @@ A [Payload CMS](https://payloadcms.com/) plugin that adds AI-powered alt text ge
|
|
|
12
12
|
- Full localization support
|
|
13
13
|
- Dashboard health widget with cached coverage insights across all configured upload collections
|
|
14
14
|
|
|
15
|
-
|
|
16
15
|
When the plugin is enabled for an upload collection, it will:
|
|
17
16
|
|
|
18
17
|
1. Add an alt text field to the collection
|
|
19
18
|
- A button to AI-generate the alt text
|
|
20
19
|
- This field will include a description of what the alt text should be
|
|
21
20
|
2. Add a keywords fields to the collection
|
|
22
|
-
- This field will be automatically filled when generating the alt text
|
|
21
|
+
- This field will be automatically filled when generating the alt text
|
|
23
22
|
- It will be used for improving the search of images in the admin panel
|
|
24
|
-
|
|
23
|
+
3. Add a bulk generate button to the collection list view
|
|
25
24
|
- This button will allow you to generate alt text for multiple images at once
|
|
26
|
-
|
|
27
|
-
- The widget is available in Payload's dashboard editor
|
|
28
|
-
- It is added to the default dashboard layout for first-time and reset layouts
|
|
25
|
+
4. Register an `Alt text health` dashboard widget
|
|
29
26
|
- Results are cached and revalidated when documents in the configured upload collections change
|
|
30
27
|
|
|
31
28
|
## Installation
|
|
@@ -39,10 +36,7 @@ pnpm add @jhb.software/payload-alt-text-plugin
|
|
|
39
36
|
Install the plugin and add it to your Payload config:
|
|
40
37
|
|
|
41
38
|
```ts
|
|
42
|
-
import {
|
|
43
|
-
payloadAltTextPlugin,
|
|
44
|
-
openAIResolver,
|
|
45
|
-
} from '@jhb.software/payload-alt-text-plugin'
|
|
39
|
+
import { payloadAltTextPlugin, openAIResolver } from '@jhb.software/payload-alt-text-plugin'
|
|
46
40
|
|
|
47
41
|
export default buildConfig({
|
|
48
42
|
plugins: [
|
|
@@ -60,25 +54,105 @@ export default buildConfig({
|
|
|
60
54
|
|
|
61
55
|
Note: When localization is disabled in your Payload config (default), you need to specify the locale to generate the alt texts in via the `locale` plugin option.
|
|
62
56
|
|
|
57
|
+
To restrict which MIME types the plugin tracks, validates, and generates for — or to override the default validator on a per-collection basis — pass an object instead of a bare slug. See [Per-collection options](#per-collection-options).
|
|
58
|
+
|
|
59
|
+
### Admin list search
|
|
60
|
+
|
|
61
|
+
By default, the plugin sets `admin.listSearchableFields` on the configured upload collections to `['filename', 'keywords', 'alt']` so the admin list-view search matches against these fields. To opt out, set `admin.listSearchableFields` on the collection yourself — any explicit value is preserved as-is:
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
{
|
|
65
|
+
slug: 'media',
|
|
66
|
+
upload: true,
|
|
67
|
+
admin: {
|
|
68
|
+
listSearchableFields: ['filename', 'alt'],
|
|
69
|
+
},
|
|
70
|
+
// ...
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
This is also the recommended escape hatch if you hit Payload's Postgres SQL-builder bug for `hasMany` localized text fields in `listSearchableFields` (see [#92](https://github.com/jhb-software/payload-plugins/issues/92)).
|
|
75
|
+
|
|
63
76
|
## Configuration
|
|
64
77
|
|
|
65
78
|
### Plugin Options
|
|
66
79
|
|
|
67
|
-
| Option | Type
|
|
68
|
-
| ---------------------------- |
|
|
69
|
-
| `collections` | `CollectionSlug[]` | Yes | Collections to enable alt text generation for
|
|
70
|
-
| `resolver` | `AltTextResolver`
|
|
71
|
-
| `getImageThumbnail` | `Function`
|
|
72
|
-
| `enabled` | `boolean`
|
|
73
|
-
| `locale` | `string`
|
|
74
|
-
| `maxBulkGenerateConcurrency` | `number`
|
|
75
|
-
| `fieldsOverride` | `Function`
|
|
76
|
-
| `healthCheck` | `boolean`
|
|
80
|
+
| Option | Type | Required | Description |
|
|
81
|
+
| ---------------------------- | ------------------------------------- | -------- | ---------------------------------------------------------------------------------------------------------------- |
|
|
82
|
+
| `collections` | `(CollectionSlug \| CollectionObj)[]` | Yes | Collections to enable alt text generation for (see [Per-collection options](#per-collection-options)) |
|
|
83
|
+
| `resolver` | `AltTextResolver` | Yes | Alt text resolver to use (e.g., `openAIResolver`) |
|
|
84
|
+
| `getImageThumbnail` | `Function` | Yes | Function to get the thumbnail URL from an image document |
|
|
85
|
+
| `enabled` | `boolean` | No | Whether to enable the plugin |
|
|
86
|
+
| `locale` | `string` | No | Locale for alt text generation (required when localization is disabled) |
|
|
87
|
+
| `maxBulkGenerateConcurrency` | `number` | No | Maximum concurrent API requests for bulk operations (default: 16) |
|
|
88
|
+
| `fieldsOverride` | `Function` | No | Override the default fields inserted by the plugin |
|
|
89
|
+
| `healthCheck` | `boolean` | No | Enable alt text health tracking: REST endpoint, cache revalidation hooks, and dashboard widget (default: `true`) |
|
|
90
|
+
|
|
91
|
+
### Per-collection options
|
|
92
|
+
|
|
93
|
+
Each entry in `collections` may be either a bare collection slug (shorthand, defaults to `['image/*']` for `mimeTypes`) or an object with the following fields:
|
|
94
|
+
|
|
95
|
+
| Option | Type | Required | Description |
|
|
96
|
+
| ----------- | ------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
97
|
+
| `slug` | `CollectionSlug` | Yes | The collection slug |
|
|
98
|
+
| `mimeTypes` | `string[]` | No | MIME types the plugin tracks, validates, and generates for. Supports wildcards like `image/*`. Defaults to `['image/*']`. |
|
|
99
|
+
| `validate` | `TextareaFieldValidation` | No | Custom validator that fully replaces the default required-alt check. Import `validateAltText` from the plugin to compose around the default behavior (see [Custom validator](#custom-validator)). |
|
|
100
|
+
|
|
101
|
+
```ts
|
|
102
|
+
payloadAltTextPlugin({
|
|
103
|
+
collections: [
|
|
104
|
+
'images', // shorthand — defaults to mimeTypes: ['image/*']
|
|
105
|
+
{ slug: 'media', mimeTypes: ['image/*', 'application/pdf'] },
|
|
106
|
+
],
|
|
107
|
+
// ...
|
|
108
|
+
})
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
#### Custom validator
|
|
112
|
+
|
|
113
|
+
The default validator requires alt text on every tracked document. Some workflows — folder moves, partial API updates, or localized setups with `fallback: false` where some locales are intentionally empty — need to skip that check when the request body does not touch `alt`. Pass a `validate` function to override the default, and compose around the exported `validateAltText` to keep the standard behavior for full updates:
|
|
114
|
+
|
|
115
|
+
```ts
|
|
116
|
+
import { payloadAltTextPlugin, validateAltText } from '@jhb.software/payload-alt-text-plugin'
|
|
117
|
+
|
|
118
|
+
payloadAltTextPlugin({
|
|
119
|
+
collections: [
|
|
120
|
+
{
|
|
121
|
+
slug: 'media',
|
|
122
|
+
validate: (value, args) => {
|
|
123
|
+
// Skip the required-alt check when the request body does not touch `alt`
|
|
124
|
+
// (e.g. folder moves, partial API updates).
|
|
125
|
+
if (!args.req.data || !('alt' in args.req.data)) return true
|
|
126
|
+
return validateAltText(value, args)
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
],
|
|
130
|
+
// ...
|
|
131
|
+
})
|
|
132
|
+
```
|
|
77
133
|
|
|
78
134
|
## Dashboard Widget
|
|
79
135
|
|
|
80
136
|
The plugin registers an `Alt text health` dashboard widget that shows alt text coverage across all configured upload collections, with cached queries that revalidate on document changes. Collections with missing alt text show a clickable badge linking to the affected images.
|
|
81
137
|
|
|
138
|
+
<img width="696" height="246" alt="image" src="https://github.com/user-attachments/assets/75df7349-0307-4047-b1ac-6b2ee0814464" />
|
|
139
|
+
|
|
140
|
+
The widget is registered under `admin.dashboard.widgets` with the slug `alt-text-health`. To show it by default on the dashboard, add it to your `admin.dashboard.defaultLayout`:
|
|
141
|
+
|
|
142
|
+
```ts
|
|
143
|
+
buildConfig({
|
|
144
|
+
admin: {
|
|
145
|
+
dashboard: {
|
|
146
|
+
defaultLayout: [
|
|
147
|
+
// ...other default widgets
|
|
148
|
+
{ widgetSlug: 'alt-text-health', width: 'full' },
|
|
149
|
+
],
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
// ...
|
|
153
|
+
})
|
|
154
|
+
```
|
|
155
|
+
|
|
82
156
|
Set `healthCheck: false` in the plugin config to disable the REST endpoint, cache revalidation hooks, and dashboard widget. If your project replaces the default dashboard via `admin.components.views.dashboard`, you need to integrate the widget into your custom dashboard yourself.
|
|
83
157
|
|
|
84
158
|
### Resolvers
|
|
@@ -117,12 +191,12 @@ export const customResolver = (): AltTextResolver => ({
|
|
|
117
191
|
}
|
|
118
192
|
},
|
|
119
193
|
resolveBulk: async ({ imageThumbnailUrl, filename, locales, req }) => {
|
|
120
|
-
|
|
194
|
+
// Your custom alt text generation logic here
|
|
121
195
|
const altTexts = await generateAltTextBulk(imageThumbnailUrl, filename, locales, req)
|
|
122
196
|
|
|
123
|
-
return {
|
|
124
|
-
success: true,
|
|
125
|
-
results: altTexts
|
|
197
|
+
return {
|
|
198
|
+
success: true,
|
|
199
|
+
results: altTexts,
|
|
126
200
|
}
|
|
127
201
|
},
|
|
128
202
|
})
|
|
@@ -138,12 +212,12 @@ Generates alt text for a single image. By default, returns the result without sa
|
|
|
138
212
|
|
|
139
213
|
**Request body:**
|
|
140
214
|
|
|
141
|
-
| Field | Type | Required | Description
|
|
142
|
-
| ------------ | ------------------ | -------- |
|
|
143
|
-
| `id` | `string \| number` | Yes | The document ID
|
|
144
|
-
| `collection` | `string` | Yes | The collection slug
|
|
145
|
-
| `locale` | `string \| null` | Yes | Target locale (use `null` for non-localized setups)
|
|
146
|
-
| `update` | `boolean` | No | When `true`, persists the result to the document (default: `false`)
|
|
215
|
+
| Field | Type | Required | Description |
|
|
216
|
+
| ------------ | ------------------ | -------- | ------------------------------------------------------------------- |
|
|
217
|
+
| `id` | `string \| number` | Yes | The document ID |
|
|
218
|
+
| `collection` | `string` | Yes | The collection slug |
|
|
219
|
+
| `locale` | `string \| null` | Yes | Target locale (use `null` for non-localized setups) |
|
|
220
|
+
| `update` | `boolean` | No | When `true`, persists the result to the document (default: `false`) |
|
|
147
221
|
|
|
148
222
|
**Response:**
|
|
149
223
|
|
|
@@ -162,10 +236,10 @@ Generates and persists alt text for multiple images across all configured locale
|
|
|
162
236
|
|
|
163
237
|
**Request body:**
|
|
164
238
|
|
|
165
|
-
| Field | Type
|
|
166
|
-
| ------------ |
|
|
167
|
-
| `collection` | `string`
|
|
168
|
-
| `ids` | `(string \| number)[]` | Yes
|
|
239
|
+
| Field | Type | Required | Description |
|
|
240
|
+
| ------------ | ---------------------- | -------- | -------------------------------- |
|
|
241
|
+
| `collection` | `string` | Yes | The collection slug |
|
|
242
|
+
| `ids` | `(string \| number)[]` | Yes | Array of document IDs to process |
|
|
169
243
|
|
|
170
244
|
**Response:**
|
|
171
245
|
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import type { TextareaFieldClientProps } from 'payload';
|
|
2
|
-
export declare const AltTextField: (clientProps: TextareaFieldClientProps) => import("react").JSX.Element;
|
|
2
|
+
export declare const AltTextField: (clientProps: TextareaFieldClientProps) => import("react").JSX.Element | null;
|
|
@@ -1,14 +1,23 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { FieldLabel, TextareaInput, useDocumentInfo, useField } from '@payloadcms/ui';
|
|
4
|
+
import { matchesMimeType } from '../utilities/mimeTypes.js';
|
|
4
5
|
import { GenerateAltTextButton } from './GenerateAltTextButton.js';
|
|
5
6
|
export const AltTextField = (clientProps)=>{
|
|
6
7
|
const { field, path } = clientProps;
|
|
7
8
|
const supportedMimeTypes = field.admin?.custom?.supportedMimeTypes;
|
|
9
|
+
const trackedMimeTypes = field.admin?.custom?.trackedMimeTypes;
|
|
8
10
|
const { setValue, value } = useField({
|
|
9
11
|
path
|
|
10
12
|
});
|
|
11
13
|
const { id } = useDocumentInfo();
|
|
14
|
+
const { value: mimeType } = useField({
|
|
15
|
+
path: 'mimeType'
|
|
16
|
+
});
|
|
17
|
+
const isTrackedMimeType = !trackedMimeTypes || trackedMimeTypes.length === 0 || !!mimeType && matchesMimeType(mimeType, trackedMimeTypes);
|
|
18
|
+
if (!isTrackedMimeType) {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
12
21
|
// the field should be optional when the document is created
|
|
13
22
|
// (since the alt text generation can only be used once the document is created and the image uploaded)
|
|
14
23
|
const required = id ? field.required : false;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/AltTextField.tsx"],"sourcesContent":["'use client'\n\nimport type { TextareaFieldClientProps } from 'payload'\n\nimport { FieldLabel, TextareaInput, useDocumentInfo, useField } from '@payloadcms/ui'\n\nimport { GenerateAltTextButton } from './GenerateAltTextButton.js'\n\nexport const AltTextField = (clientProps: TextareaFieldClientProps) => {\n const { field, path } = clientProps\n\n const supportedMimeTypes = field.admin?.custom?.supportedMimeTypes as string[] | undefined\n\n const { setValue, value } = useField<string>({ path })\n const { id } = useDocumentInfo()\n\n // the field should be optional when the document is created\n // (since the alt text generation can only be used once the document is created and the image uploaded)\n const required = id ? field.required : false\n\n return (\n <div className=\"field-type textarea\" style={{ flex: '1 1 auto' }}>\n <FieldLabel\n htmlFor={`field-${path}`}\n label={field.label}\n localized={field.localized}\n required={required}\n />\n\n <div className=\"field-type__wrap\">\n <TextareaInput\n AfterInput={<GenerateAltTextButton supportedMimeTypes={supportedMimeTypes} />}\n onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) => setValue(e.target.value)}\n path={path}\n required={required}\n value={value}\n />\n </div>\n </div>\n )\n}\n"],"names":["FieldLabel","TextareaInput","useDocumentInfo","useField","GenerateAltTextButton","AltTextField","clientProps","field","path","supportedMimeTypes","admin","custom","setValue","value","id","required","div","className","style","flex","htmlFor","label","localized","AfterInput","onChange","e","target"],"mappings":"AAAA;;AAIA,SAASA,UAAU,EAAEC,aAAa,EAAEC,eAAe,EAAEC,QAAQ,QAAQ,iBAAgB;AAErF,SAASC,qBAAqB,QAAQ,6BAA4B;AAElE,OAAO,MAAMC,eAAe,CAACC;IAC3B,MAAM,EAAEC,KAAK,EAAEC,IAAI,EAAE,GAAGF;IAExB,MAAMG,qBAAqBF,MAAMG,KAAK,EAAEC,QAAQF;
|
|
1
|
+
{"version":3,"sources":["../../src/components/AltTextField.tsx"],"sourcesContent":["'use client'\n\nimport type { TextareaFieldClientProps } from 'payload'\n\nimport { FieldLabel, TextareaInput, useDocumentInfo, useField } from '@payloadcms/ui'\n\nimport { matchesMimeType } from '../utilities/mimeTypes.js'\nimport { GenerateAltTextButton } from './GenerateAltTextButton.js'\n\nexport const AltTextField = (clientProps: TextareaFieldClientProps) => {\n const { field, path } = clientProps\n\n const supportedMimeTypes = field.admin?.custom?.supportedMimeTypes as string[] | undefined\n const trackedMimeTypes = field.admin?.custom?.trackedMimeTypes as string[] | undefined\n\n const { setValue, value } = useField<string>({ path })\n const { id } = useDocumentInfo()\n const { value: mimeType } = useField<string>({ path: 'mimeType' })\n\n const isTrackedMimeType =\n !trackedMimeTypes ||\n trackedMimeTypes.length === 0 ||\n (!!mimeType && matchesMimeType(mimeType, trackedMimeTypes))\n\n if (!isTrackedMimeType) {\n return null\n }\n\n // the field should be optional when the document is created\n // (since the alt text generation can only be used once the document is created and the image uploaded)\n const required = id ? field.required : false\n\n return (\n <div className=\"field-type textarea\" style={{ flex: '1 1 auto' }}>\n <FieldLabel\n htmlFor={`field-${path}`}\n label={field.label}\n localized={field.localized}\n required={required}\n />\n\n <div className=\"field-type__wrap\">\n <TextareaInput\n AfterInput={<GenerateAltTextButton supportedMimeTypes={supportedMimeTypes} />}\n onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) => setValue(e.target.value)}\n path={path}\n required={required}\n value={value}\n />\n </div>\n </div>\n )\n}\n"],"names":["FieldLabel","TextareaInput","useDocumentInfo","useField","matchesMimeType","GenerateAltTextButton","AltTextField","clientProps","field","path","supportedMimeTypes","admin","custom","trackedMimeTypes","setValue","value","id","mimeType","isTrackedMimeType","length","required","div","className","style","flex","htmlFor","label","localized","AfterInput","onChange","e","target"],"mappings":"AAAA;;AAIA,SAASA,UAAU,EAAEC,aAAa,EAAEC,eAAe,EAAEC,QAAQ,QAAQ,iBAAgB;AAErF,SAASC,eAAe,QAAQ,4BAA2B;AAC3D,SAASC,qBAAqB,QAAQ,6BAA4B;AAElE,OAAO,MAAMC,eAAe,CAACC;IAC3B,MAAM,EAAEC,KAAK,EAAEC,IAAI,EAAE,GAAGF;IAExB,MAAMG,qBAAqBF,MAAMG,KAAK,EAAEC,QAAQF;IAChD,MAAMG,mBAAmBL,MAAMG,KAAK,EAAEC,QAAQC;IAE9C,MAAM,EAAEC,QAAQ,EAAEC,KAAK,EAAE,GAAGZ,SAAiB;QAAEM;IAAK;IACpD,MAAM,EAAEO,EAAE,EAAE,GAAGd;IACf,MAAM,EAAEa,OAAOE,QAAQ,EAAE,GAAGd,SAAiB;QAAEM,MAAM;IAAW;IAEhE,MAAMS,oBACJ,CAACL,oBACDA,iBAAiBM,MAAM,KAAK,KAC3B,CAAC,CAACF,YAAYb,gBAAgBa,UAAUJ;IAE3C,IAAI,CAACK,mBAAmB;QACtB,OAAO;IACT;IAEA,4DAA4D;IAC5D,uGAAuG;IACvG,MAAME,WAAWJ,KAAKR,MAAMY,QAAQ,GAAG;IAEvC,qBACE,MAACC;QAAIC,WAAU;QAAsBC,OAAO;YAAEC,MAAM;QAAW;;0BAC7D,KAACxB;gBACCyB,SAAS,CAAC,MAAM,EAAEhB,MAAM;gBACxBiB,OAAOlB,MAAMkB,KAAK;gBAClBC,WAAWnB,MAAMmB,SAAS;gBAC1BP,UAAUA;;0BAGZ,KAACC;gBAAIC,WAAU;0BACb,cAAA,KAACrB;oBACC2B,0BAAY,KAACvB;wBAAsBK,oBAAoBA;;oBACvDmB,UAAU,CAACC,IAA8ChB,SAASgB,EAAEC,MAAM,CAAChB,KAAK;oBAChFN,MAAMA;oBACNW,UAAUA;oBACVL,OAAOA;;;;;AAKjB,EAAC"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { Pill } from '@payloadcms/ui/elements/Pill';
|
|
3
|
+
import { formatAdminURL } from 'payload/shared';
|
|
3
4
|
import { getAltTextHealthWidgetData } from '../utilities/altTextHealth.js';
|
|
4
5
|
import { getAltTextHealthWidgetDisplayState } from '../utilities/altTextHealthWidgetDisplay.js';
|
|
5
6
|
import { getCollectionLabel } from '../utilities/getCollectionLabel.js';
|
|
@@ -9,6 +10,7 @@ import { ImageIcon } from './icons/ImageIcon.js';
|
|
|
9
10
|
export async function AltTextHealthWidget({ req }) {
|
|
10
11
|
const t = req.t;
|
|
11
12
|
const { collections, errors, isLocalized, localeCount, totalDocs } = await getAltTextHealthWidgetData(req);
|
|
13
|
+
const adminRoute = req.payload.config.routes.admin;
|
|
12
14
|
return /*#__PURE__*/ _jsxs("div", {
|
|
13
15
|
className: "card",
|
|
14
16
|
style: {
|
|
@@ -102,7 +104,10 @@ export async function AltTextHealthWidget({ req }) {
|
|
|
102
104
|
},
|
|
103
105
|
children: [
|
|
104
106
|
/*#__PURE__*/ _jsx("a", {
|
|
105
|
-
href:
|
|
107
|
+
href: formatAdminURL({
|
|
108
|
+
adminRoute,
|
|
109
|
+
path: `/collections/${collection.collection}`
|
|
110
|
+
}),
|
|
106
111
|
style: {
|
|
107
112
|
color: 'var(--theme-text)',
|
|
108
113
|
fontSize: '14px',
|
|
@@ -151,7 +156,10 @@ export async function AltTextHealthWidget({ req }) {
|
|
|
151
156
|
displayState === 'unhealthy' && collection.invalidDocIds && collection.invalidDocIds.length > 0 ? /*#__PURE__*/ _jsx(Pill, {
|
|
152
157
|
pillStyle: "error",
|
|
153
158
|
size: "small",
|
|
154
|
-
to: `${
|
|
159
|
+
to: `${formatAdminURL({
|
|
160
|
+
adminRoute,
|
|
161
|
+
path: `/collections/${collection.collection}`
|
|
162
|
+
})}?where[id][in]=${collection.invalidDocIds.join(',')}`,
|
|
155
163
|
children: /*#__PURE__*/ _jsxs("div", {
|
|
156
164
|
style: {
|
|
157
165
|
alignItems: 'center',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/AltTextHealthWidget.tsx"],"sourcesContent":["import type { TFunction } from '@payloadcms/translations'\nimport type { WidgetServerProps } from 'payload'\n\nimport { Pill } from '@payloadcms/ui/elements/Pill'\n\nimport type { PluginAltTextTranslationKeys } from '../translations/index.js'\n\nimport { getAltTextHealthWidgetData } from '../utilities/altTextHealth.js'\nimport { getAltTextHealthWidgetDisplayState } from '../utilities/altTextHealthWidgetDisplay.js'\nimport { getCollectionLabel } from '../utilities/getCollectionLabel.js'\nimport { ArrowRightIcon } from './icons/ArrowRightIcon.js'\nimport { CheckIcon } from './icons/CheckIcon.js'\nimport { ImageIcon } from './icons/ImageIcon.js'\n\nexport async function AltTextHealthWidget({ req }: WidgetServerProps) {\n const t = req.t as TFunction<PluginAltTextTranslationKeys>\n const { collections, errors, isLocalized, localeCount, totalDocs } =\n await getAltTextHealthWidgetData(req)\n\n return (\n <div\n className=\"card\"\n style={{\n display: 'flex',\n flexDirection: 'column',\n gap: '16px',\n height: '100%',\n }}\n >\n <div style={{ display: 'flex', flexDirection: 'column', gap: '4px' }}>\n <div style={{ alignItems: 'center', display: 'flex', gap: '0.5rem' }}>\n <div style={{ color: 'var(--theme-elevation-500)' }}>\n <ImageIcon />\n </div>\n <h3 style={{ margin: 0 }}>\n {t('@jhb.software/payload-alt-text-plugin:altTextHealthWidget')}\n </h3>\n </div>\n <p style={{ color: 'var(--theme-text)', fontSize: '14px', margin: 0, opacity: 0.75 }}>\n {t('@jhb.software/payload-alt-text-plugin:altTextHealthDescription')}\n </p>\n </div>\n\n {totalDocs === 0 && errors.length === 0 && (\n <p style={{ color: 'var(--theme-text)', margin: 0, opacity: 0.75 }}>\n {t('@jhb.software/payload-alt-text-plugin:noImagesFound')}\n </p>\n )}\n\n {errors.length > 0 && (\n <p style={{ color: '#92400e', fontSize: '13px', margin: 0 }}>\n {t('@jhb.software/payload-alt-text-plugin:healthCheckPartialWarning')}\n </p>\n )}\n\n <div style={{ display: 'flex', flexDirection: 'column', gap: '10px' }}>\n {collections.map((collection) => {\n const displayState = getAltTextHealthWidgetDisplayState(collection)\n\n return (\n <div\n key={collection.collection}\n style={{\n alignItems: 'center',\n background: 'var(--theme-elevation-50)',\n border: '1px solid var(--theme-border-color)',\n borderRadius: 'var(--style-radius-m)',\n display: 'flex',\n gap: '12px',\n justifyContent: 'space-between',\n padding: '12px 16px',\n }}\n >\n <div style={{ display: 'flex', flexDirection: 'column', gap: '2px', minWidth: 0 }}>\n <a\n href={`${req.payload.config.routes.admin}/collections/${collection.collection}`}\n style={{\n color: 'var(--theme-text)',\n fontSize: '14px',\n fontWeight: 500,\n textDecoration: 'none',\n }}\n >\n {getCollectionLabel(\n collection.collection,\n req.payload.config.collections,\n req.locale,\n )}\n </a>\n\n {displayState === 'unavailable' ? (\n <span style={{ color: '#92400e', fontSize: '13px' }}>\n {t('@jhb.software/payload-alt-text-plugin:collectionCheckFailed')}\n </span>\n ) : (\n <span style={{ fontSize: '13px', opacity: 0.7 }}>\n <span style={{ whiteSpace: 'nowrap' }}>\n {t('@jhb.software/payload-alt-text-plugin:totalImageCount', {\n count: collection.totalDocs,\n })}\n </span>\n {isLocalized && (\n <>\n {' · '}\n <span style={{ whiteSpace: 'nowrap' }}>\n {t('@jhb.software/payload-alt-text-plugin:localeCount', {\n count: localeCount,\n })}\n </span>\n </>\n )}\n </span>\n )}\n </div>\n\n {displayState === 'unhealthy' &&\n collection.invalidDocIds &&\n collection.invalidDocIds.length > 0 ? (\n <Pill\n pillStyle=\"error\"\n size=\"small\"\n to={`${req.payload.config.routes.admin}/collections/${collection.collection}?where[id][in]=${collection.invalidDocIds.join(',')}`}\n >\n <div style={{ alignItems: 'center', display: 'flex', gap: '0.25rem' }}>\n {t('@jhb.software/payload-alt-text-plugin:statusUnhealthy', {\n count: collection.missingDocs + collection.partialDocs,\n })}\n <ArrowRightIcon height=\"12\" width=\"12\" />\n </div>\n </Pill>\n ) : displayState === 'healthy' ? (\n <Pill pillStyle=\"success\" size=\"small\">\n <div style={{ alignItems: 'center', display: 'flex', gap: '0.25rem' }}>\n {t('@jhb.software/payload-alt-text-plugin:statusHealthy')}\n <CheckIcon height=\"12\" width=\"12\" />\n </div>\n </Pill>\n ) : displayState === 'unhealthy' ? (\n <Pill pillStyle=\"error\" size=\"small\">\n {t('@jhb.software/payload-alt-text-plugin:statusUnhealthy', {\n count: collection.missingDocs + collection.partialDocs,\n })}\n </Pill>\n ) : (\n <Pill pillStyle=\"warning\" size=\"small\">\n {t('@jhb.software/payload-alt-text-plugin:collectionCheckFailed')}\n </Pill>\n )}\n </div>\n )\n })}\n </div>\n </div>\n )\n}\n"],"names":["Pill","getAltTextHealthWidgetData","getAltTextHealthWidgetDisplayState","getCollectionLabel","ArrowRightIcon","CheckIcon","ImageIcon","AltTextHealthWidget","req","t","collections","errors","isLocalized","localeCount","totalDocs","div","className","style","display","flexDirection","gap","height","alignItems","color","h3","margin","p","fontSize","opacity","length","map","collection","displayState","background","border","borderRadius","justifyContent","padding","minWidth","a","href","payload","config","routes","admin","fontWeight","textDecoration","locale","span","whiteSpace","count","invalidDocIds","pillStyle","size","to","join","missingDocs","partialDocs","width"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../src/components/AltTextHealthWidget.tsx"],"sourcesContent":["import type { TFunction } from '@payloadcms/translations'\nimport type { WidgetServerProps } from 'payload'\n\nimport { Pill } from '@payloadcms/ui/elements/Pill'\nimport { formatAdminURL } from 'payload/shared'\n\nimport type { PluginAltTextTranslationKeys } from '../translations/index.js'\n\nimport { getAltTextHealthWidgetData } from '../utilities/altTextHealth.js'\nimport { getAltTextHealthWidgetDisplayState } from '../utilities/altTextHealthWidgetDisplay.js'\nimport { getCollectionLabel } from '../utilities/getCollectionLabel.js'\nimport { ArrowRightIcon } from './icons/ArrowRightIcon.js'\nimport { CheckIcon } from './icons/CheckIcon.js'\nimport { ImageIcon } from './icons/ImageIcon.js'\n\nexport async function AltTextHealthWidget({ req }: WidgetServerProps) {\n const t = req.t as TFunction<PluginAltTextTranslationKeys>\n const { collections, errors, isLocalized, localeCount, totalDocs } =\n await getAltTextHealthWidgetData(req)\n const adminRoute = req.payload.config.routes.admin\n\n return (\n <div\n className=\"card\"\n style={{\n display: 'flex',\n flexDirection: 'column',\n gap: '16px',\n height: '100%',\n }}\n >\n <div style={{ display: 'flex', flexDirection: 'column', gap: '4px' }}>\n <div style={{ alignItems: 'center', display: 'flex', gap: '0.5rem' }}>\n <div style={{ color: 'var(--theme-elevation-500)' }}>\n <ImageIcon />\n </div>\n <h3 style={{ margin: 0 }}>\n {t('@jhb.software/payload-alt-text-plugin:altTextHealthWidget')}\n </h3>\n </div>\n <p style={{ color: 'var(--theme-text)', fontSize: '14px', margin: 0, opacity: 0.75 }}>\n {t('@jhb.software/payload-alt-text-plugin:altTextHealthDescription')}\n </p>\n </div>\n\n {totalDocs === 0 && errors.length === 0 && (\n <p style={{ color: 'var(--theme-text)', margin: 0, opacity: 0.75 }}>\n {t('@jhb.software/payload-alt-text-plugin:noImagesFound')}\n </p>\n )}\n\n {errors.length > 0 && (\n <p style={{ color: '#92400e', fontSize: '13px', margin: 0 }}>\n {t('@jhb.software/payload-alt-text-plugin:healthCheckPartialWarning')}\n </p>\n )}\n\n <div style={{ display: 'flex', flexDirection: 'column', gap: '10px' }}>\n {collections.map((collection) => {\n const displayState = getAltTextHealthWidgetDisplayState(collection)\n\n return (\n <div\n key={collection.collection}\n style={{\n alignItems: 'center',\n background: 'var(--theme-elevation-50)',\n border: '1px solid var(--theme-border-color)',\n borderRadius: 'var(--style-radius-m)',\n display: 'flex',\n gap: '12px',\n justifyContent: 'space-between',\n padding: '12px 16px',\n }}\n >\n <div style={{ display: 'flex', flexDirection: 'column', gap: '2px', minWidth: 0 }}>\n <a\n href={formatAdminURL({\n adminRoute,\n path: `/collections/${collection.collection}`,\n })}\n style={{\n color: 'var(--theme-text)',\n fontSize: '14px',\n fontWeight: 500,\n textDecoration: 'none',\n }}\n >\n {getCollectionLabel(\n collection.collection,\n req.payload.config.collections,\n req.locale,\n )}\n </a>\n\n {displayState === 'unavailable' ? (\n <span style={{ color: '#92400e', fontSize: '13px' }}>\n {t('@jhb.software/payload-alt-text-plugin:collectionCheckFailed')}\n </span>\n ) : (\n <span style={{ fontSize: '13px', opacity: 0.7 }}>\n <span style={{ whiteSpace: 'nowrap' }}>\n {t('@jhb.software/payload-alt-text-plugin:totalImageCount', {\n count: collection.totalDocs,\n })}\n </span>\n {isLocalized && (\n <>\n {' · '}\n <span style={{ whiteSpace: 'nowrap' }}>\n {t('@jhb.software/payload-alt-text-plugin:localeCount', {\n count: localeCount,\n })}\n </span>\n </>\n )}\n </span>\n )}\n </div>\n\n {displayState === 'unhealthy' &&\n collection.invalidDocIds &&\n collection.invalidDocIds.length > 0 ? (\n <Pill\n pillStyle=\"error\"\n size=\"small\"\n to={`${formatAdminURL({\n adminRoute,\n path: `/collections/${collection.collection}`,\n })}?where[id][in]=${collection.invalidDocIds.join(',')}`}\n >\n <div style={{ alignItems: 'center', display: 'flex', gap: '0.25rem' }}>\n {t('@jhb.software/payload-alt-text-plugin:statusUnhealthy', {\n count: collection.missingDocs + collection.partialDocs,\n })}\n <ArrowRightIcon height=\"12\" width=\"12\" />\n </div>\n </Pill>\n ) : displayState === 'healthy' ? (\n <Pill pillStyle=\"success\" size=\"small\">\n <div style={{ alignItems: 'center', display: 'flex', gap: '0.25rem' }}>\n {t('@jhb.software/payload-alt-text-plugin:statusHealthy')}\n <CheckIcon height=\"12\" width=\"12\" />\n </div>\n </Pill>\n ) : displayState === 'unhealthy' ? (\n <Pill pillStyle=\"error\" size=\"small\">\n {t('@jhb.software/payload-alt-text-plugin:statusUnhealthy', {\n count: collection.missingDocs + collection.partialDocs,\n })}\n </Pill>\n ) : (\n <Pill pillStyle=\"warning\" size=\"small\">\n {t('@jhb.software/payload-alt-text-plugin:collectionCheckFailed')}\n </Pill>\n )}\n </div>\n )\n })}\n </div>\n </div>\n )\n}\n"],"names":["Pill","formatAdminURL","getAltTextHealthWidgetData","getAltTextHealthWidgetDisplayState","getCollectionLabel","ArrowRightIcon","CheckIcon","ImageIcon","AltTextHealthWidget","req","t","collections","errors","isLocalized","localeCount","totalDocs","adminRoute","payload","config","routes","admin","div","className","style","display","flexDirection","gap","height","alignItems","color","h3","margin","p","fontSize","opacity","length","map","collection","displayState","background","border","borderRadius","justifyContent","padding","minWidth","a","href","path","fontWeight","textDecoration","locale","span","whiteSpace","count","invalidDocIds","pillStyle","size","to","join","missingDocs","partialDocs","width"],"mappings":"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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
-
import { Button, toast, useSelection, useTranslation } from '@payloadcms/ui';
|
|
3
|
+
import { Button, toast, useConfig, useSelection, useTranslation } from '@payloadcms/ui';
|
|
4
4
|
import { useRouter } from 'next/navigation.js';
|
|
5
5
|
import { useTransition } from 'react';
|
|
6
6
|
import { Lightning } from './icons/Lightning.js';
|
|
@@ -9,6 +9,7 @@ export function BulkGenerateAltTextsButton({ collectionSlug }) {
|
|
|
9
9
|
const { t } = useTranslation();
|
|
10
10
|
const [isPending, startTransition] = useTransition();
|
|
11
11
|
const { selected, setSelection } = useSelection();
|
|
12
|
+
const { config: { routes: { api: apiRoute }, serverURL } } = useConfig();
|
|
12
13
|
const selectedIds = Array.from(selected.entries()).filter(([, isSelected])=>isSelected).map(([id])=>id);
|
|
13
14
|
const router = useRouter();
|
|
14
15
|
const handleGenerateAltTexts = ()=>{
|
|
@@ -17,7 +18,7 @@ export function BulkGenerateAltTextsButton({ collectionSlug }) {
|
|
|
17
18
|
throw new Error('Collection slug is required');
|
|
18
19
|
}
|
|
19
20
|
try {
|
|
20
|
-
const response = await fetch('/
|
|
21
|
+
const response = await fetch(`${serverURL ?? ''}${apiRoute}/alt-text-plugin/generate/bulk`, {
|
|
21
22
|
body: JSON.stringify({
|
|
22
23
|
collection: collectionSlug,
|
|
23
24
|
ids: selectedIds
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/BulkGenerateAltTextsButton.tsx"],"sourcesContent":["'use client'\n\nimport { Button, toast, useSelection, useTranslation } from '@payloadcms/ui'\nimport { useRouter } from 'next/navigation.js'\nimport { useTransition } from 'react'\n\nimport type {\n PluginAltTextTranslationKeys,\n PluginAltTextTranslations,\n} from '../translations/index.js'\n\nimport { Lightning } from './icons/Lightning.js'\nimport { Spinner } from './icons/Spinner.js'\n\nexport function BulkGenerateAltTextsButton({ collectionSlug }: { collectionSlug: string }) {\n const { t } = useTranslation<PluginAltTextTranslations, PluginAltTextTranslationKeys>()\n const [isPending, startTransition] = useTransition()\n const { selected, setSelection } = useSelection()\n\n const selectedIds = Array.from(selected.entries())\n .filter(([, isSelected]) => isSelected)\n .map(([id]) => id) as string[]\n\n const router = useRouter()\n\n const handleGenerateAltTexts = () => {\n startTransition(async () => {\n if (!collectionSlug) {\n throw new Error('Collection slug is required')\n }\n\n try {\n const response = await fetch('/
|
|
1
|
+
{"version":3,"sources":["../../src/components/BulkGenerateAltTextsButton.tsx"],"sourcesContent":["'use client'\n\nimport { Button, toast, useConfig, useSelection, useTranslation } from '@payloadcms/ui'\nimport { useRouter } from 'next/navigation.js'\nimport { useTransition } from 'react'\n\nimport type {\n PluginAltTextTranslationKeys,\n PluginAltTextTranslations,\n} from '../translations/index.js'\n\nimport { Lightning } from './icons/Lightning.js'\nimport { Spinner } from './icons/Spinner.js'\n\nexport function BulkGenerateAltTextsButton({ collectionSlug }: { collectionSlug: string }) {\n const { t } = useTranslation<PluginAltTextTranslations, PluginAltTextTranslationKeys>()\n const [isPending, startTransition] = useTransition()\n const { selected, setSelection } = useSelection()\n const {\n config: {\n routes: { api: apiRoute },\n serverURL,\n },\n } = useConfig()\n\n const selectedIds = Array.from(selected.entries())\n .filter(([, isSelected]) => isSelected)\n .map(([id]) => id) as string[]\n\n const router = useRouter()\n\n const handleGenerateAltTexts = () => {\n startTransition(async () => {\n if (!collectionSlug) {\n throw new Error('Collection slug is required')\n }\n\n try {\n const response = await fetch(\n `${serverURL ?? ''}${apiRoute}/alt-text-plugin/generate/bulk`,\n {\n body: JSON.stringify({\n collection: collectionSlug,\n ids: selectedIds,\n }),\n method: 'POST',\n },\n )\n\n if (!response.ok) {\n toast.error(t('@jhb.software/payload-alt-text-plugin:failedToGenerate'))\n return\n }\n\n const data = (await response.json()) as {\n erroredDocs: string[]\n totalDocs: number\n updatedDocs: number\n }\n\n if (data.erroredDocs.length > 0) {\n toast.error(\n t('@jhb.software/payload-alt-text-plugin:failedToGenerateForXImages', {\n count: data.erroredDocs.length,\n }),\n )\n }\n\n // in case not all images were updated, show a warning instead of a success message:\n if (data.updatedDocs === data.totalDocs) {\n toast.success(\n t('@jhb.software/payload-alt-text-plugin:xOfYImagesUpdated', {\n total: data.totalDocs,\n updated: data.updatedDocs,\n }),\n )\n } else {\n toast.warning(\n t('@jhb.software/payload-alt-text-plugin:xOfYImagesUpdated', {\n total: data.totalDocs,\n updated: data.updatedDocs,\n }),\n )\n }\n\n // deselect all previously selected images\n for (const id of selectedIds) {\n setSelection(id)\n }\n\n router.refresh()\n } catch (error) {\n console.error('Error generating alt text:', error)\n toast.error(t('@jhb.software/payload-alt-text-plugin:errorGeneratingAltText'))\n }\n })\n }\n\n return (\n selectedIds.length > 0 && (\n <div className=\"m-0\" style={{ display: 'flex', justifyContent: 'right' }}>\n <Button\n className=\"m-0\"\n disabled={isPending || selectedIds.length === 0}\n icon={isPending ? <Spinner /> : <Lightning />}\n onClick={handleGenerateAltTexts}\n >\n {t('@jhb.software/payload-alt-text-plugin:generateAltTextFor', {\n count: selectedIds.length,\n })}\n </Button>\n </div>\n )\n )\n}\n"],"names":["Button","toast","useConfig","useSelection","useTranslation","useRouter","useTransition","Lightning","Spinner","BulkGenerateAltTextsButton","collectionSlug","t","isPending","startTransition","selected","setSelection","config","routes","api","apiRoute","serverURL","selectedIds","Array","from","entries","filter","isSelected","map","id","router","handleGenerateAltTexts","Error","response","fetch","body","JSON","stringify","collection","ids","method","ok","error","data","json","erroredDocs","length","count","updatedDocs","totalDocs","success","total","updated","warning","refresh","console","div","className","style","display","justifyContent","disabled","icon","onClick"],"mappings":"AAAA;;AAEA,SAASA,MAAM,EAAEC,KAAK,EAAEC,SAAS,EAAEC,YAAY,EAAEC,cAAc,QAAQ,iBAAgB;AACvF,SAASC,SAAS,QAAQ,qBAAoB;AAC9C,SAASC,aAAa,QAAQ,QAAO;AAOrC,SAASC,SAAS,QAAQ,uBAAsB;AAChD,SAASC,OAAO,QAAQ,qBAAoB;AAE5C,OAAO,SAASC,2BAA2B,EAAEC,cAAc,EAA8B;IACvF,MAAM,EAAEC,CAAC,EAAE,GAAGP;IACd,MAAM,CAACQ,WAAWC,gBAAgB,GAAGP;IACrC,MAAM,EAAEQ,QAAQ,EAAEC,YAAY,EAAE,GAAGZ;IACnC,MAAM,EACJa,QAAQ,EACNC,QAAQ,EAAEC,KAAKC,QAAQ,EAAE,EACzBC,SAAS,EACV,EACF,GAAGlB;IAEJ,MAAMmB,cAAcC,MAAMC,IAAI,CAACT,SAASU,OAAO,IAC5CC,MAAM,CAAC,CAAC,GAAGC,WAAW,GAAKA,YAC3BC,GAAG,CAAC,CAAC,CAACC,GAAG,GAAKA;IAEjB,MAAMC,SAASxB;IAEf,MAAMyB,yBAAyB;QAC7BjB,gBAAgB;YACd,IAAI,CAACH,gBAAgB;gBACnB,MAAM,IAAIqB,MAAM;YAClB;YAEA,IAAI;gBACF,MAAMC,WAAW,MAAMC,MACrB,GAAGb,aAAa,KAAKD,SAAS,8BAA8B,CAAC,EAC7D;oBACEe,MAAMC,KAAKC,SAAS,CAAC;wBACnBC,YAAY3B;wBACZ4B,KAAKjB;oBACP;oBACAkB,QAAQ;gBACV;gBAGF,IAAI,CAACP,SAASQ,EAAE,EAAE;oBAChBvC,MAAMwC,KAAK,CAAC9B,EAAE;oBACd;gBACF;gBAEA,MAAM+B,OAAQ,MAAMV,SAASW,IAAI;gBAMjC,IAAID,KAAKE,WAAW,CAACC,MAAM,GAAG,GAAG;oBAC/B5C,MAAMwC,KAAK,CACT9B,EAAE,oEAAoE;wBACpEmC,OAAOJ,KAAKE,WAAW,CAACC,MAAM;oBAChC;gBAEJ;gBAEA,oFAAoF;gBACpF,IAAIH,KAAKK,WAAW,KAAKL,KAAKM,SAAS,EAAE;oBACvC/C,MAAMgD,OAAO,CACXtC,EAAE,2DAA2D;wBAC3DuC,OAAOR,KAAKM,SAAS;wBACrBG,SAAST,KAAKK,WAAW;oBAC3B;gBAEJ,OAAO;oBACL9C,MAAMmD,OAAO,CACXzC,EAAE,2DAA2D;wBAC3DuC,OAAOR,KAAKM,SAAS;wBACrBG,SAAST,KAAKK,WAAW;oBAC3B;gBAEJ;gBAEA,0CAA0C;gBAC1C,KAAK,MAAMnB,MAAMP,YAAa;oBAC5BN,aAAaa;gBACf;gBAEAC,OAAOwB,OAAO;YAChB,EAAE,OAAOZ,OAAO;gBACda,QAAQb,KAAK,CAAC,8BAA8BA;gBAC5CxC,MAAMwC,KAAK,CAAC9B,EAAE;YAChB;QACF;IACF;IAEA,OACEU,YAAYwB,MAAM,GAAG,mBACnB,KAACU;QAAIC,WAAU;QAAMC,OAAO;YAAEC,SAAS;YAAQC,gBAAgB;QAAQ;kBACrE,cAAA,KAAC3D;YACCwD,WAAU;YACVI,UAAUhD,aAAaS,YAAYwB,MAAM,KAAK;YAC9CgB,MAAMjD,0BAAY,KAACJ,6BAAa,KAACD;YACjCuD,SAAShC;sBAERnB,EAAE,4DAA4D;gBAC7DmC,OAAOzB,YAAYwB,MAAM;YAC3B;;;AAKV"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { Button, toast, useDocumentInfo, useField, useLocale, useTranslation } from '@payloadcms/ui';
|
|
3
|
+
import { Button, toast, useConfig, useDocumentInfo, useField, useLocale, useTranslation } from '@payloadcms/ui';
|
|
4
4
|
import { useTransition } from 'react';
|
|
5
5
|
import { Lightning } from './icons/Lightning.js';
|
|
6
6
|
import { Spinner } from './icons/Spinner.js';
|
|
@@ -9,6 +9,7 @@ export function GenerateAltTextButton({ supportedMimeTypes }) {
|
|
|
9
9
|
const { id, collectionSlug } = useDocumentInfo();
|
|
10
10
|
const locale = useLocale();
|
|
11
11
|
const [isPending, startTransition] = useTransition();
|
|
12
|
+
const { config: { routes: { api: apiRoute }, serverURL } } = useConfig();
|
|
12
13
|
const { setValue: setKeywords } = useField({
|
|
13
14
|
path: 'keywords'
|
|
14
15
|
});
|
|
@@ -26,7 +27,7 @@ export function GenerateAltTextButton({ supportedMimeTypes }) {
|
|
|
26
27
|
}
|
|
27
28
|
startTransition(async ()=>{
|
|
28
29
|
try {
|
|
29
|
-
const response = await fetch('/
|
|
30
|
+
const response = await fetch(`${serverURL ?? ''}${apiRoute}/alt-text-plugin/generate`, {
|
|
30
31
|
body: JSON.stringify({
|
|
31
32
|
id: id,
|
|
32
33
|
collection: collectionSlug,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/GenerateAltTextButton.tsx"],"sourcesContent":["'use client'\n\nimport {
|
|
1
|
+
{"version":3,"sources":["../../src/components/GenerateAltTextButton.tsx"],"sourcesContent":["'use client'\n\nimport {\n Button,\n toast,\n useConfig,\n useDocumentInfo,\n useField,\n useLocale,\n useTranslation,\n} from '@payloadcms/ui'\nimport { useTransition } from 'react'\n\nimport type {\n PluginAltTextTranslationKeys,\n PluginAltTextTranslations,\n} from '../translations/index.js'\n\nimport { Lightning } from './icons/Lightning.js'\nimport { Spinner } from './icons/Spinner.js'\n\nexport function GenerateAltTextButton({ supportedMimeTypes }: { supportedMimeTypes?: string[] }) {\n const { t } = useTranslation<PluginAltTextTranslations, PluginAltTextTranslationKeys>()\n const { id, collectionSlug } = useDocumentInfo()\n const locale = useLocale()\n const [isPending, startTransition] = useTransition()\n const {\n config: {\n routes: { api: apiRoute },\n serverURL,\n },\n } = useConfig()\n\n const { setValue: setKeywords } = useField<string>({ path: 'keywords' })\n const { setValue: setAltText } = useField<string>({ path: 'alt' })\n const { value: mimeType } = useField<string>({ path: 'mimeType' })\n\n const isUnsupportedMimeType =\n !!mimeType && !!supportedMimeTypes && !supportedMimeTypes.includes(mimeType)\n\n const handleGenerateAltText = () => {\n if (!collectionSlug || !id) {\n toast.error(t('@jhb.software/payload-alt-text-plugin:cannotGenerateMissingFields'))\n throw new Error('Missing required fields')\n }\n\n startTransition(async () => {\n try {\n const response = await fetch(`${serverURL ?? ''}${apiRoute}/alt-text-plugin/generate`, {\n body: JSON.stringify({\n id: id as string,\n collection: collectionSlug,\n locale: locale?.code ?? null, // sent null when localization is disabled\n }),\n method: 'POST',\n })\n\n if (!response.ok) {\n let errorMessage = t('@jhb.software/payload-alt-text-plugin:failedToGenerate')\n try {\n const errorData = (await response.json()) as { error: string }\n errorMessage = errorData.error\n } catch (error) {\n console.error('Error generating alt text:', error)\n }\n\n toast.error(errorMessage)\n return\n }\n\n const data = (await response.json()) as {\n altText: string\n keywords: string[]\n }\n\n if (data.altText && data.keywords) {\n setAltText(data.altText)\n setKeywords(data.keywords)\n toast.success(t('@jhb.software/payload-alt-text-plugin:altTextGeneratedSuccess'))\n } else {\n toast.error(t('@jhb.software/payload-alt-text-plugin:noAltTextGenerated'))\n }\n } catch (error) {\n console.error('Error generating alt text:', error)\n toast.error(t('@jhb.software/payload-alt-text-plugin:errorGeneratingAltText'))\n }\n })\n }\n\n return (\n <div style={{ display: 'flex', gap: '20px', marginTop: '10px' }}>\n <div style={{ color: 'var(--theme-elevation-400)', flex: '1' }}>\n <p>{t('@jhb.software/payload-alt-text-plugin:altTextDescription')}</p>\n <ol style={{ margin: '10px 0', paddingLeft: '20px' }}>\n <li>{t('@jhb.software/payload-alt-text-plugin:altTextRequirement1')}</li>\n <li>{t('@jhb.software/payload-alt-text-plugin:altTextRequirement2')}</li>\n <li>{t('@jhb.software/payload-alt-text-plugin:altTextRequirement3')}</li>\n </ol>\n </div>\n <div style={{ alignItems: 'center', display: 'flex' }}>\n <Button\n disabled={isPending || !id || isUnsupportedMimeType}\n icon={isPending ? <Spinner /> : <Lightning />}\n onClick={handleGenerateAltText}\n tooltip={\n isUnsupportedMimeType\n ? t('@jhb.software/payload-alt-text-plugin:unsupportedMimeType', { mimeType })\n : !id\n ? t('@jhb.software/payload-alt-text-plugin:pleaseSaveDocumentFirst')\n : undefined\n }\n >\n {t('@jhb.software/payload-alt-text-plugin:generateAltText')}\n </Button>\n </div>\n </div>\n )\n}\n"],"names":["Button","toast","useConfig","useDocumentInfo","useField","useLocale","useTranslation","useTransition","Lightning","Spinner","GenerateAltTextButton","supportedMimeTypes","t","id","collectionSlug","locale","isPending","startTransition","config","routes","api","apiRoute","serverURL","setValue","setKeywords","path","setAltText","value","mimeType","isUnsupportedMimeType","includes","handleGenerateAltText","error","Error","response","fetch","body","JSON","stringify","collection","code","method","ok","errorMessage","errorData","json","console","data","altText","keywords","success","div","style","display","gap","marginTop","color","flex","p","ol","margin","paddingLeft","li","alignItems","disabled","icon","onClick","tooltip","undefined"],"mappings":"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"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import pMap from 'p-map';
|
|
2
2
|
import { z, ZodError } from 'zod';
|
|
3
3
|
import { localesFromConfig } from '../utilities/localesFromConfig.js';
|
|
4
|
+
import { matchesMimeType } from '../utilities/mimeTypes.js';
|
|
4
5
|
/**
|
|
5
6
|
* Generates and updates alt text for multiple images in all locales.
|
|
6
7
|
*/ export const bulkGenerateAltTextsEndpoint = (access)=>async (req)=>{
|
|
@@ -111,6 +112,10 @@ async function generateAndUpdateAltText({ id, collection, locales, payload, plug
|
|
|
111
112
|
throw new Error('Image not found');
|
|
112
113
|
}
|
|
113
114
|
const mimeType = 'mimeType' in imageDoc && typeof imageDoc.mimeType === 'string' ? imageDoc.mimeType : undefined;
|
|
115
|
+
const collectionConfig = pluginConfig.collections.find((entry)=>entry.slug === collection);
|
|
116
|
+
if (mimeType && collectionConfig && !matchesMimeType(mimeType, collectionConfig.mimeTypes)) {
|
|
117
|
+
throw new Error(`Alt text is not tracked for files of type "${mimeType}" in the "${collection}" collection. Tracked types: ${collectionConfig.mimeTypes.join(', ')}.`);
|
|
118
|
+
}
|
|
114
119
|
if (mimeType && pluginConfig.resolver.supportedMimeTypes && !pluginConfig.resolver.supportedMimeTypes.includes(mimeType)) {
|
|
115
120
|
throw new Error(`Alt text generation is not supported for files of type "${mimeType}". Supported types: ${pluginConfig.resolver.supportedMimeTypes.join(', ')}.`);
|
|
116
121
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/endpoints/bulkGenerateAltTexts.ts"],"sourcesContent":["import type { BasePayload, CollectionSlug, PayloadHandler, PayloadRequest } from 'payload'\n\nimport pMap from 'p-map'\nimport { z, ZodError } from 'zod'\n\nimport type { AltTextPluginConfig } from '../types/AltTextPluginConfig.js'\n\nimport { localesFromConfig } from '../utilities/localesFromConfig.js'\n\n/**\n * Generates and updates alt text for multiple images in all locales.\n */\nexport const bulkGenerateAltTextsEndpoint =\n (access: AltTextPluginConfig['access']): PayloadHandler =>\n async (req: PayloadRequest) => {\n try {\n if (!(await access({ req }))) {\n return Response.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n const data = 'json' in req && typeof req.json === 'function' ? await req.json() : null\n\n const schema = z.object({\n collection: z.string(),\n ids: z.array(z.union([z.string(), z.number()])),\n })\n\n const { collection, ids } = schema.parse(data)\n\n let updatedDocs = 0\n const erroredDocs: (number | string)[] = []\n\n // Get plugin config from payload config\n const pluginConfig = req.payload.config.custom?.altTextPluginConfig as\n | AltTextPluginConfig\n | undefined\n\n if (!pluginConfig) {\n return Response.json({ error: 'Plugin config not found' }, { status: 500 })\n }\n\n if (!pluginConfig.resolver) {\n return Response.json({ error: 'No alt text resolver configured' }, { status: 500 })\n }\n\n const concurrency = pluginConfig.maxBulkGenerateConcurrency\n\n // determine target locales based on config\n const locales = localesFromConfig(req.payload.config)\n const targetLocales = locales ?? [pluginConfig.locale!]\n if (!targetLocales) {\n return Response.json(\n {\n error:\n 'Could not determine target locales for alt text generation. Please check your plugin configuration.',\n },\n { status: 500 },\n )\n }\n\n await pMap(\n ids,\n async (id) => {\n try {\n await generateAndUpdateAltText({\n id,\n collection,\n locales: targetLocales,\n payload: req.payload,\n pluginConfig,\n req,\n })\n updatedDocs++\n console.log(\n `${updatedDocs}/${ids.length} updated (${Math.round((updatedDocs / ids.length) * 100)}%)`,\n )\n } catch (error) {\n console.error(`Error generating alt text for ${id}:`, error)\n erroredDocs.push(id)\n }\n },\n { concurrency },\n )\n\n if (erroredDocs.length > 0) {\n console.error(`Failed for: ${erroredDocs.join(', ')}`)\n }\n\n return Response.json({\n erroredDocs,\n totalDocs: ids.length,\n updatedDocs,\n })\n } catch (error) {\n if (error instanceof ZodError) {\n return Response.json(\n {\n details: error.issues.map((e) => ({\n message: e.message,\n path: e.path.join('.'),\n })),\n error: 'Validation failed',\n },\n { status: 400 },\n )\n }\n console.error('Error in bulk generation:', error)\n return Response.json(\n {\n error: `Error generating alt text: ${error instanceof Error ? error.message : 'Unknown error'}`,\n },\n { status: 500 },\n )\n }\n }\n\nasync function generateAndUpdateAltText({\n id,\n collection,\n locales,\n payload,\n pluginConfig,\n req,\n}: {\n collection: CollectionSlug\n id: number | string\n locales: string[]\n payload: BasePayload\n pluginConfig: AltTextPluginConfig\n req: PayloadRequest\n}) {\n const imageDoc = await payload.findByID({\n id,\n collection,\n depth: 0,\n })\n\n if (!imageDoc) {\n throw new Error('Image not found')\n }\n\n const mimeType =\n 'mimeType' in imageDoc && typeof imageDoc.mimeType === 'string' ? imageDoc.mimeType : undefined\n\n if (\n mimeType &&\n pluginConfig.resolver.supportedMimeTypes &&\n !pluginConfig.resolver.supportedMimeTypes.includes(mimeType)\n ) {\n throw new Error(\n `Alt text generation is not supported for files of type \"${mimeType}\". Supported types: ${pluginConfig.resolver.supportedMimeTypes.join(', ')}.`,\n )\n }\n\n const imageThumbnailUrl = pluginConfig.getImageThumbnail(imageDoc)\n\n const result = await pluginConfig.resolver.resolveBulk({\n filename:\n 'filename' in imageDoc && typeof imageDoc.filename === 'string'\n ? imageDoc.filename\n : undefined,\n imageThumbnailUrl,\n locales,\n req,\n })\n\n if (!result.success) {\n throw new Error(result.error || 'Failed to generate alt text')\n }\n\n for (const locale of locales) {\n const localeResult = result.results[locale]\n if (localeResult) {\n await payload.update({\n id,\n collection,\n data: {\n alt: localeResult.altText,\n keywords: localeResult.keywords,\n },\n locale,\n })\n }\n }\n}\n"],"names":["pMap","z","ZodError","localesFromConfig","bulkGenerateAltTextsEndpoint","access","req","Response","json","error","status","data","schema","object","collection","string","ids","array","union","number","parse","updatedDocs","erroredDocs","pluginConfig","payload","config","custom","altTextPluginConfig","resolver","concurrency","maxBulkGenerateConcurrency","locales","targetLocales","locale","id","generateAndUpdateAltText","console","log","length","Math","round","push","join","totalDocs","details","issues","map","e","message","path","Error","imageDoc","findByID","depth","mimeType","undefined","supportedMimeTypes","includes","imageThumbnailUrl","getImageThumbnail","result","resolveBulk","filename","success","localeResult","results","update","alt","altText","keywords"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../src/endpoints/bulkGenerateAltTexts.ts"],"sourcesContent":["import type { BasePayload, CollectionSlug, PayloadHandler, PayloadRequest } from 'payload'\n\nimport pMap from 'p-map'\nimport { z, ZodError } from 'zod'\n\nimport type { AltTextPluginConfig } from '../types/AltTextPluginConfig.js'\n\nimport { localesFromConfig } from '../utilities/localesFromConfig.js'\nimport { matchesMimeType } from '../utilities/mimeTypes.js'\n\n/**\n * Generates and updates alt text for multiple images in all locales.\n */\nexport const bulkGenerateAltTextsEndpoint =\n (access: AltTextPluginConfig['access']): PayloadHandler =>\n async (req: PayloadRequest) => {\n try {\n if (!(await access({ req }))) {\n return Response.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n const data = 'json' in req && typeof req.json === 'function' ? await req.json() : null\n\n const schema = z.object({\n collection: z.string(),\n ids: z.array(z.union([z.string(), z.number()])),\n })\n\n const { collection, ids } = schema.parse(data)\n\n let updatedDocs = 0\n const erroredDocs: (number | string)[] = []\n\n // Get plugin config from payload config\n const pluginConfig = req.payload.config.custom?.altTextPluginConfig as\n | AltTextPluginConfig\n | undefined\n\n if (!pluginConfig) {\n return Response.json({ error: 'Plugin config not found' }, { status: 500 })\n }\n\n if (!pluginConfig.resolver) {\n return Response.json({ error: 'No alt text resolver configured' }, { status: 500 })\n }\n\n const concurrency = pluginConfig.maxBulkGenerateConcurrency\n\n // determine target locales based on config\n const locales = localesFromConfig(req.payload.config)\n const targetLocales = locales ?? [pluginConfig.locale!]\n if (!targetLocales) {\n return Response.json(\n {\n error:\n 'Could not determine target locales for alt text generation. Please check your plugin configuration.',\n },\n { status: 500 },\n )\n }\n\n await pMap(\n ids,\n async (id) => {\n try {\n await generateAndUpdateAltText({\n id,\n collection,\n locales: targetLocales,\n payload: req.payload,\n pluginConfig,\n req,\n })\n updatedDocs++\n console.log(\n `${updatedDocs}/${ids.length} updated (${Math.round((updatedDocs / ids.length) * 100)}%)`,\n )\n } catch (error) {\n console.error(`Error generating alt text for ${id}:`, error)\n erroredDocs.push(id)\n }\n },\n { concurrency },\n )\n\n if (erroredDocs.length > 0) {\n console.error(`Failed for: ${erroredDocs.join(', ')}`)\n }\n\n return Response.json({\n erroredDocs,\n totalDocs: ids.length,\n updatedDocs,\n })\n } catch (error) {\n if (error instanceof ZodError) {\n return Response.json(\n {\n details: error.issues.map((e) => ({\n message: e.message,\n path: e.path.join('.'),\n })),\n error: 'Validation failed',\n },\n { status: 400 },\n )\n }\n console.error('Error in bulk generation:', error)\n return Response.json(\n {\n error: `Error generating alt text: ${error instanceof Error ? error.message : 'Unknown error'}`,\n },\n { status: 500 },\n )\n }\n }\n\nasync function generateAndUpdateAltText({\n id,\n collection,\n locales,\n payload,\n pluginConfig,\n req,\n}: {\n collection: CollectionSlug\n id: number | string\n locales: string[]\n payload: BasePayload\n pluginConfig: AltTextPluginConfig\n req: PayloadRequest\n}) {\n const imageDoc = await payload.findByID({\n id,\n collection,\n depth: 0,\n })\n\n if (!imageDoc) {\n throw new Error('Image not found')\n }\n\n const mimeType =\n 'mimeType' in imageDoc && typeof imageDoc.mimeType === 'string' ? imageDoc.mimeType : undefined\n\n const collectionConfig = pluginConfig.collections.find((entry) => entry.slug === collection)\n\n if (mimeType && collectionConfig && !matchesMimeType(mimeType, collectionConfig.mimeTypes)) {\n throw new Error(\n `Alt text is not tracked for files of type \"${mimeType}\" in the \"${collection}\" collection. Tracked types: ${collectionConfig.mimeTypes.join(', ')}.`,\n )\n }\n\n if (\n mimeType &&\n pluginConfig.resolver.supportedMimeTypes &&\n !pluginConfig.resolver.supportedMimeTypes.includes(mimeType)\n ) {\n throw new Error(\n `Alt text generation is not supported for files of type \"${mimeType}\". Supported types: ${pluginConfig.resolver.supportedMimeTypes.join(', ')}.`,\n )\n }\n\n const imageThumbnailUrl = pluginConfig.getImageThumbnail(imageDoc)\n\n const result = await pluginConfig.resolver.resolveBulk({\n filename:\n 'filename' in imageDoc && typeof imageDoc.filename === 'string'\n ? imageDoc.filename\n : undefined,\n imageThumbnailUrl,\n locales,\n req,\n })\n\n if (!result.success) {\n throw new Error(result.error || 'Failed to generate alt text')\n }\n\n for (const locale of locales) {\n const localeResult = result.results[locale]\n if (localeResult) {\n await payload.update({\n id,\n collection,\n data: {\n alt: localeResult.altText,\n keywords: localeResult.keywords,\n },\n locale,\n })\n }\n }\n}\n"],"names":["pMap","z","ZodError","localesFromConfig","matchesMimeType","bulkGenerateAltTextsEndpoint","access","req","Response","json","error","status","data","schema","object","collection","string","ids","array","union","number","parse","updatedDocs","erroredDocs","pluginConfig","payload","config","custom","altTextPluginConfig","resolver","concurrency","maxBulkGenerateConcurrency","locales","targetLocales","locale","id","generateAndUpdateAltText","console","log","length","Math","round","push","join","totalDocs","details","issues","map","e","message","path","Error","imageDoc","findByID","depth","mimeType","undefined","collectionConfig","collections","find","entry","slug","mimeTypes","supportedMimeTypes","includes","imageThumbnailUrl","getImageThumbnail","result","resolveBulk","filename","success","localeResult","results","update","alt","altText","keywords"],"mappings":"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"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z, ZodError } from 'zod';
|
|
2
|
+
import { matchesMimeType } from '../utilities/mimeTypes.js';
|
|
2
3
|
/**
|
|
3
4
|
* Generates alt text for a single image using the configured resolver.
|
|
4
5
|
*
|
|
@@ -64,6 +65,14 @@ import { z, ZodError } from 'zod';
|
|
|
64
65
|
});
|
|
65
66
|
}
|
|
66
67
|
const mimeType = 'mimeType' in imageDoc && typeof imageDoc.mimeType === 'string' ? imageDoc.mimeType : undefined;
|
|
68
|
+
const collectionConfig = pluginConfig.collections.find((entry)=>entry.slug === collection);
|
|
69
|
+
if (mimeType && collectionConfig && !matchesMimeType(mimeType, collectionConfig.mimeTypes)) {
|
|
70
|
+
return Response.json({
|
|
71
|
+
error: `Alt text is not tracked for files of type "${mimeType}" in the "${collection}" collection. Tracked types: ${collectionConfig.mimeTypes.join(', ')}.`
|
|
72
|
+
}, {
|
|
73
|
+
status: 400
|
|
74
|
+
});
|
|
75
|
+
}
|
|
67
76
|
if (mimeType && pluginConfig.resolver.supportedMimeTypes && !pluginConfig.resolver.supportedMimeTypes.includes(mimeType)) {
|
|
68
77
|
return Response.json({
|
|
69
78
|
error: `Alt text generation is not supported for files of type "${mimeType}". Supported types: ${pluginConfig.resolver.supportedMimeTypes.join(', ')}.`
|