@jhb.software/payload-alt-text-plugin 0.4.2 → 0.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/AltTextField.js +4 -1
- package/dist/components/AltTextField.js.map +1 -1
- package/dist/components/AltTextHealthWidget.js +85 -77
- package/dist/components/AltTextHealthWidget.js.map +1 -1
- package/dist/components/BulkGenerateAltTextsButton.js +11 -3
- package/dist/components/BulkGenerateAltTextsButton.js.map +1 -1
- package/dist/components/GenerateAltTextButton.d.ts +3 -1
- package/dist/components/GenerateAltTextButton.js +9 -3
- package/dist/components/GenerateAltTextButton.js.map +1 -1
- package/dist/components/icons/ArrowRightIcon.d.ts +2 -0
- package/dist/components/icons/ArrowRightIcon.js +22 -0
- package/dist/components/icons/ArrowRightIcon.js.map +1 -0
- package/dist/components/icons/CheckIcon.d.ts +2 -0
- package/dist/components/icons/CheckIcon.js +22 -0
- package/dist/components/icons/CheckIcon.js.map +1 -0
- package/dist/components/icons/ImageIcon.d.ts +2 -0
- package/dist/components/icons/ImageIcon.js +38 -0
- package/dist/components/icons/ImageIcon.js.map +1 -0
- package/dist/endpoints/bulkGenerateAltTexts.js +4 -0
- package/dist/endpoints/bulkGenerateAltTexts.js.map +1 -1
- package/dist/endpoints/generateAltText.js +8 -0
- package/dist/endpoints/generateAltText.js.map +1 -1
- package/dist/fields/altTextField.d.ts +2 -1
- package/dist/fields/altTextField.js +4 -1
- package/dist/fields/altTextField.js.map +1 -1
- package/dist/plugin.js +2 -1
- package/dist/plugin.js.map +1 -1
- package/dist/resolvers/openAI.js +8 -1
- package/dist/resolvers/openAI.js.map +1 -1
- package/dist/resolvers/types.d.ts +2 -0
- package/dist/resolvers/types.js.map +1 -1
- package/dist/translations/de.js +10 -9
- package/dist/translations/de.js.map +1 -1
- package/dist/translations/en.js +9 -8
- package/dist/translations/en.js.map +1 -1
- package/package.json +3 -1
|
@@ -4,6 +4,7 @@ import { FieldLabel, TextareaInput, useDocumentInfo, useField } from '@payloadcm
|
|
|
4
4
|
import { GenerateAltTextButton } from './GenerateAltTextButton.js';
|
|
5
5
|
export const AltTextField = (clientProps)=>{
|
|
6
6
|
const { field, path } = clientProps;
|
|
7
|
+
const supportedMimeTypes = field.admin?.custom?.supportedMimeTypes;
|
|
7
8
|
const { setValue, value } = useField({
|
|
8
9
|
path
|
|
9
10
|
});
|
|
@@ -26,7 +27,9 @@ export const AltTextField = (clientProps)=>{
|
|
|
26
27
|
/*#__PURE__*/ _jsx("div", {
|
|
27
28
|
className: "field-type__wrap",
|
|
28
29
|
children: /*#__PURE__*/ _jsx(TextareaInput, {
|
|
29
|
-
AfterInput: /*#__PURE__*/ _jsx(GenerateAltTextButton, {
|
|
30
|
+
AfterInput: /*#__PURE__*/ _jsx(GenerateAltTextButton, {
|
|
31
|
+
supportedMimeTypes: supportedMimeTypes
|
|
32
|
+
}),
|
|
30
33
|
onChange: (e)=>setValue(e.target.value),
|
|
31
34
|
path: path,
|
|
32
35
|
required: required,
|
|
@@ -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 { 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 />}\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","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,MAAM,EAAEG,QAAQ,EAAEC,KAAK,EAAE,
|
|
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;IAEhD,MAAM,EAAEG,QAAQ,EAAEC,KAAK,EAAE,GAAGV,SAAiB;QAAEK;IAAK;IACpD,MAAM,EAAEM,EAAE,EAAE,GAAGZ;IAEf,4DAA4D;IAC5D,uGAAuG;IACvG,MAAMa,WAAWD,KAAKP,MAAMQ,QAAQ,GAAG;IAEvC,qBACE,MAACC;QAAIC,WAAU;QAAsBC,OAAO;YAAEC,MAAM;QAAW;;0BAC7D,KAACnB;gBACCoB,SAAS,CAAC,MAAM,EAAEZ,MAAM;gBACxBa,OAAOd,MAAMc,KAAK;gBAClBC,WAAWf,MAAMe,SAAS;gBAC1BP,UAAUA;;0BAGZ,KAACC;gBAAIC,WAAU;0BACb,cAAA,KAAChB;oBACCsB,0BAAY,KAACnB;wBAAsBK,oBAAoBA;;oBACvDe,UAAU,CAACC,IAA8Cb,SAASa,EAAEC,MAAM,CAACb,KAAK;oBAChFL,MAAMA;oBACNO,UAAUA;oBACVF,OAAOA;;;;;AAKjB,EAAC"}
|
|
@@ -1,19 +1,12 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { Pill } from '@payloadcms/ui/elements/Pill';
|
|
2
3
|
import { getAltTextHealthWidgetData } from '../utilities/altTextHealth.js';
|
|
3
4
|
import { getAltTextHealthWidgetDisplayState } from '../utilities/altTextHealthWidgetDisplay.js';
|
|
4
5
|
import { getCollectionLabel } from '../utilities/getCollectionLabel.js';
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
color: '#15803d'
|
|
9
|
-
},
|
|
10
|
-
unhealthy: {
|
|
11
|
-
background: '#fee2e2',
|
|
12
|
-
color: '#991b1b'
|
|
13
|
-
}
|
|
14
|
-
};
|
|
6
|
+
import { ArrowRightIcon } from './icons/ArrowRightIcon.js';
|
|
7
|
+
import { CheckIcon } from './icons/CheckIcon.js';
|
|
8
|
+
import { ImageIcon } from './icons/ImageIcon.js';
|
|
15
9
|
export async function AltTextHealthWidget({ req }) {
|
|
16
|
-
// Plugin translation keys are not in Payload's built-in key union
|
|
17
10
|
const t = req.t;
|
|
18
11
|
const { collections, errors, isLocalized, localeCount, totalDocs } = await getAltTextHealthWidgetData(req);
|
|
19
12
|
return /*#__PURE__*/ _jsxs("div", {
|
|
@@ -32,13 +25,26 @@ export async function AltTextHealthWidget({ req }) {
|
|
|
32
25
|
gap: '4px'
|
|
33
26
|
},
|
|
34
27
|
children: [
|
|
35
|
-
/*#__PURE__*/
|
|
28
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
36
29
|
style: {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
30
|
+
alignItems: 'center',
|
|
31
|
+
display: 'flex',
|
|
32
|
+
gap: '0.5rem'
|
|
40
33
|
},
|
|
41
|
-
children:
|
|
34
|
+
children: [
|
|
35
|
+
/*#__PURE__*/ _jsx("div", {
|
|
36
|
+
style: {
|
|
37
|
+
color: 'var(--theme-elevation-500)'
|
|
38
|
+
},
|
|
39
|
+
children: /*#__PURE__*/ _jsx(ImageIcon, {})
|
|
40
|
+
}),
|
|
41
|
+
/*#__PURE__*/ _jsx("h3", {
|
|
42
|
+
style: {
|
|
43
|
+
margin: 0
|
|
44
|
+
},
|
|
45
|
+
children: t('@jhb.software/payload-alt-text-plugin:altTextHealthWidget')
|
|
46
|
+
})
|
|
47
|
+
]
|
|
42
48
|
}),
|
|
43
49
|
/*#__PURE__*/ _jsx("p", {
|
|
44
50
|
style: {
|
|
@@ -80,7 +86,7 @@ export async function AltTextHealthWidget({ req }) {
|
|
|
80
86
|
alignItems: 'center',
|
|
81
87
|
background: 'var(--theme-elevation-50)',
|
|
82
88
|
border: '1px solid var(--theme-border-color)',
|
|
83
|
-
borderRadius: '
|
|
89
|
+
borderRadius: 'var(--style-radius-m)',
|
|
84
90
|
display: 'flex',
|
|
85
91
|
gap: '12px',
|
|
86
92
|
justifyContent: 'space-between',
|
|
@@ -117,75 +123,77 @@ export async function AltTextHealthWidget({ req }) {
|
|
|
117
123
|
opacity: 0.7
|
|
118
124
|
},
|
|
119
125
|
children: [
|
|
120
|
-
|
|
126
|
+
/*#__PURE__*/ _jsx("span", {
|
|
127
|
+
style: {
|
|
128
|
+
whiteSpace: 'nowrap'
|
|
129
|
+
},
|
|
130
|
+
children: t('@jhb.software/payload-alt-text-plugin:totalImageCount', {
|
|
131
|
+
count: collection.totalDocs
|
|
132
|
+
})
|
|
133
|
+
}),
|
|
121
134
|
isLocalized && /*#__PURE__*/ _jsxs(_Fragment, {
|
|
122
135
|
children: [
|
|
123
136
|
' · ',
|
|
124
|
-
|
|
137
|
+
/*#__PURE__*/ _jsx("span", {
|
|
138
|
+
style: {
|
|
139
|
+
whiteSpace: 'nowrap'
|
|
140
|
+
},
|
|
141
|
+
children: t('@jhb.software/payload-alt-text-plugin:localeCount', {
|
|
142
|
+
count: localeCount
|
|
143
|
+
})
|
|
144
|
+
})
|
|
125
145
|
]
|
|
126
146
|
})
|
|
127
147
|
]
|
|
128
148
|
})
|
|
129
149
|
]
|
|
130
150
|
}),
|
|
131
|
-
displayState === 'unhealthy' && collection.invalidDocIds && collection.invalidDocIds.length > 0 ? /*#__PURE__*/
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
collection.missingDocs + collection.partialDocs,
|
|
178
|
-
' ',
|
|
179
|
-
t('@jhb.software/payload-alt-text-plugin:statusUnhealthy')
|
|
180
|
-
]
|
|
181
|
-
}) : /*#__PURE__*/ _jsx("span", {
|
|
182
|
-
style: {
|
|
183
|
-
color: '#92400e',
|
|
184
|
-
flexShrink: 0,
|
|
185
|
-
fontSize: '12px',
|
|
186
|
-
fontWeight: 600,
|
|
187
|
-
whiteSpace: 'nowrap'
|
|
188
|
-
},
|
|
151
|
+
displayState === 'unhealthy' && collection.invalidDocIds && collection.invalidDocIds.length > 0 ? /*#__PURE__*/ _jsx(Pill, {
|
|
152
|
+
pillStyle: "error",
|
|
153
|
+
size: "small",
|
|
154
|
+
to: `${req.payload.config.routes.admin}/collections/${collection.collection}?where[id][in]=${collection.invalidDocIds.join(',')}`,
|
|
155
|
+
children: /*#__PURE__*/ _jsxs("div", {
|
|
156
|
+
style: {
|
|
157
|
+
alignItems: 'center',
|
|
158
|
+
display: 'flex',
|
|
159
|
+
gap: '0.25rem'
|
|
160
|
+
},
|
|
161
|
+
children: [
|
|
162
|
+
t('@jhb.software/payload-alt-text-plugin:statusUnhealthy', {
|
|
163
|
+
count: collection.missingDocs + collection.partialDocs
|
|
164
|
+
}),
|
|
165
|
+
/*#__PURE__*/ _jsx(ArrowRightIcon, {
|
|
166
|
+
height: "12",
|
|
167
|
+
width: "12"
|
|
168
|
+
})
|
|
169
|
+
]
|
|
170
|
+
})
|
|
171
|
+
}) : displayState === 'healthy' ? /*#__PURE__*/ _jsx(Pill, {
|
|
172
|
+
pillStyle: "success",
|
|
173
|
+
size: "small",
|
|
174
|
+
children: /*#__PURE__*/ _jsxs("div", {
|
|
175
|
+
style: {
|
|
176
|
+
alignItems: 'center',
|
|
177
|
+
display: 'flex',
|
|
178
|
+
gap: '0.25rem'
|
|
179
|
+
},
|
|
180
|
+
children: [
|
|
181
|
+
t('@jhb.software/payload-alt-text-plugin:statusHealthy'),
|
|
182
|
+
/*#__PURE__*/ _jsx(CheckIcon, {
|
|
183
|
+
height: "12",
|
|
184
|
+
width: "12"
|
|
185
|
+
})
|
|
186
|
+
]
|
|
187
|
+
})
|
|
188
|
+
}) : displayState === 'unhealthy' ? /*#__PURE__*/ _jsx(Pill, {
|
|
189
|
+
pillStyle: "error",
|
|
190
|
+
size: "small",
|
|
191
|
+
children: t('@jhb.software/payload-alt-text-plugin:statusUnhealthy', {
|
|
192
|
+
count: collection.missingDocs + collection.partialDocs
|
|
193
|
+
})
|
|
194
|
+
}) : /*#__PURE__*/ _jsx(Pill, {
|
|
195
|
+
pillStyle: "warning",
|
|
196
|
+
size: "small",
|
|
189
197
|
children: t('@jhb.software/payload-alt-text-plugin:collectionCheckFailed')
|
|
190
198
|
})
|
|
191
199
|
]
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/AltTextHealthWidget.tsx"],"sourcesContent":["import type { WidgetServerProps } from 'payload'\n\nimport { getAltTextHealthWidgetData } from '../utilities/altTextHealth.js'\nimport { getAltTextHealthWidgetDisplayState } from '../utilities/altTextHealthWidgetDisplay.js'\nimport { getCollectionLabel } from '../utilities/getCollectionLabel.js'\n\nconst badgeStyles = {\n healthy: { background: '#dcfce7', color: '#15803d' },\n unhealthy: { background: '#fee2e2', color: '#991b1b' },\n}\n\nexport async function AltTextHealthWidget({ req }: WidgetServerProps) {\n // Plugin translation keys are not in Payload's built-in key union\n const t = req.t as (key: string) => string\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 <h3 style={{ fontSize: '18px', fontWeight: 600, margin: 0 }}>\n {t('@jhb.software/payload-alt-text-plugin:altTextHealthWidget')}\n </h3>\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: '10px',\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 {t('@jhb.software/payload-alt-text-plugin:totalImageCount').replace(\n '{count}',\n String(collection.totalDocs),\n )}\n {isLocalized && (\n <>\n {' · '}\n {t('@jhb.software/payload-alt-text-plugin:localeCount').replace(\n '{count}',\n String(localeCount),\n )}\n </>\n )}\n </span>\n )}\n </div>\n\n {displayState === 'unhealthy' &&\n collection.invalidDocIds &&\n collection.invalidDocIds.length > 0 ? (\n <a\n href={`${req.payload.config.routes.admin}/collections/${collection.collection}?where[id][in]=${collection.invalidDocIds.join(',')}`}\n style={{\n background: badgeStyles.unhealthy.background,\n borderRadius: '999px',\n color: badgeStyles.unhealthy.color,\n flexShrink: 0,\n fontSize: '12px',\n fontWeight: 600,\n padding: '4px 10px',\n textDecoration: 'none',\n whiteSpace: 'nowrap',\n }}\n >\n {collection.missingDocs + collection.partialDocs}{' '}\n {t('@jhb.software/payload-alt-text-plugin:statusUnhealthy')} →\n </a>\n ) : displayState === 'healthy' ? (\n <span\n style={{\n background: badgeStyles.healthy.background,\n borderRadius: '999px',\n color: badgeStyles.healthy.color,\n flexShrink: 0,\n fontSize: '12px',\n fontWeight: 600,\n padding: '4px 10px',\n whiteSpace: 'nowrap',\n }}\n >\n {t('@jhb.software/payload-alt-text-plugin:statusHealthy')} ✓\n </span>\n ) : displayState === 'unhealthy' ? (\n <span\n style={{\n background: badgeStyles.unhealthy.background,\n borderRadius: '999px',\n color: badgeStyles.unhealthy.color,\n flexShrink: 0,\n fontSize: '12px',\n fontWeight: 600,\n padding: '4px 10px',\n whiteSpace: 'nowrap',\n }}\n >\n {collection.missingDocs + collection.partialDocs}{' '}\n {t('@jhb.software/payload-alt-text-plugin:statusUnhealthy')}\n </span>\n ) : (\n <span\n style={{\n color: '#92400e',\n flexShrink: 0,\n fontSize: '12px',\n fontWeight: 600,\n whiteSpace: 'nowrap',\n }}\n >\n {t('@jhb.software/payload-alt-text-plugin:collectionCheckFailed')}\n </span>\n )}\n </div>\n )\n })}\n </div>\n </div>\n )\n}\n"],"names":["getAltTextHealthWidgetData","getAltTextHealthWidgetDisplayState","getCollectionLabel","badgeStyles","healthy","background","color","unhealthy","AltTextHealthWidget","req","t","collections","errors","isLocalized","localeCount","totalDocs","div","className","style","display","flexDirection","gap","height","h3","fontSize","fontWeight","margin","p","opacity","length","map","collection","displayState","alignItems","border","borderRadius","justifyContent","padding","minWidth","a","href","payload","config","routes","admin","textDecoration","locale","span","replace","String","invalidDocIds","join","flexShrink","whiteSpace","missingDocs","partialDocs"],"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'\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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"}
|
|
@@ -30,13 +30,21 @@ export function BulkGenerateAltTextsButton({ collectionSlug }) {
|
|
|
30
30
|
}
|
|
31
31
|
const data = await response.json();
|
|
32
32
|
if (data.erroredDocs.length > 0) {
|
|
33
|
-
toast.error(t('@jhb.software/payload-alt-text-plugin:failedToGenerateForXImages'
|
|
33
|
+
toast.error(t('@jhb.software/payload-alt-text-plugin:failedToGenerateForXImages', {
|
|
34
|
+
count: data.erroredDocs.length
|
|
35
|
+
}));
|
|
34
36
|
}
|
|
35
37
|
// in case not all images were updated, show a warning instead of a success message:
|
|
36
38
|
if (data.updatedDocs === data.totalDocs) {
|
|
37
|
-
toast.success(t('@jhb.software/payload-alt-text-plugin:xOfYImagesUpdated'
|
|
39
|
+
toast.success(t('@jhb.software/payload-alt-text-plugin:xOfYImagesUpdated', {
|
|
40
|
+
total: data.totalDocs,
|
|
41
|
+
updated: data.updatedDocs
|
|
42
|
+
}));
|
|
38
43
|
} else {
|
|
39
|
-
toast.warning(t('@jhb.software/payload-alt-text-plugin:xOfYImagesUpdated'
|
|
44
|
+
toast.warning(t('@jhb.software/payload-alt-text-plugin:xOfYImagesUpdated', {
|
|
45
|
+
total: data.totalDocs,
|
|
46
|
+
updated: data.updatedDocs
|
|
47
|
+
}));
|
|
40
48
|
}
|
|
41
49
|
// deselect all previously selected images
|
|
42
50
|
for (const id of 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('/api/alt-text-plugin/generate/bulk', {\n body: JSON.stringify({\n collection: collectionSlug,\n ids: selectedIds,\n }),\n method: 'POST',\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'
|
|
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('/api/alt-text-plugin/generate/bulk', {\n body: JSON.stringify({\n collection: collectionSlug,\n ids: selectedIds,\n }),\n method: 'POST',\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')} {selectedIds.length}{' '}\n {selectedIds.length === 1\n ? t('@jhb.software/payload-alt-text-plugin:image')\n : t('@jhb.software/payload-alt-text-plugin:images')}\n </Button>\n </div>\n )\n )\n}\n"],"names":["Button","toast","useSelection","useTranslation","useRouter","useTransition","Lightning","Spinner","BulkGenerateAltTextsButton","collectionSlug","t","isPending","startTransition","selected","setSelection","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,YAAY,EAAEC,cAAc,QAAQ,iBAAgB;AAC5E,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;IAEnC,MAAMa,cAAcC,MAAMC,IAAI,CAACJ,SAASK,OAAO,IAC5CC,MAAM,CAAC,CAAC,GAAGC,WAAW,GAAKA,YAC3BC,GAAG,CAAC,CAAC,CAACC,GAAG,GAAKA;IAEjB,MAAMC,SAASnB;IAEf,MAAMoB,yBAAyB;QAC7BZ,gBAAgB;YACd,IAAI,CAACH,gBAAgB;gBACnB,MAAM,IAAIgB,MAAM;YAClB;YAEA,IAAI;gBACF,MAAMC,WAAW,MAAMC,MAAM,sCAAsC;oBACjEC,MAAMC,KAAKC,SAAS,CAAC;wBACnBC,YAAYtB;wBACZuB,KAAKjB;oBACP;oBACAkB,QAAQ;gBACV;gBAEA,IAAI,CAACP,SAASQ,EAAE,EAAE;oBAChBjC,MAAMkC,KAAK,CAACzB,EAAE;oBACd;gBACF;gBAEA,MAAM0B,OAAQ,MAAMV,SAASW,IAAI;gBAMjC,IAAID,KAAKE,WAAW,CAACC,MAAM,GAAG,GAAG;oBAC/BtC,MAAMkC,KAAK,CACTzB,EAAE,oEAAoE;wBACpE8B,OAAOJ,KAAKE,WAAW,CAACC,MAAM;oBAChC;gBAEJ;gBAEA,oFAAoF;gBACpF,IAAIH,KAAKK,WAAW,KAAKL,KAAKM,SAAS,EAAE;oBACvCzC,MAAM0C,OAAO,CACXjC,EAAE,2DAA2D;wBAC3DkC,OAAOR,KAAKM,SAAS;wBACrBG,SAAST,KAAKK,WAAW;oBAC3B;gBAEJ,OAAO;oBACLxC,MAAM6C,OAAO,CACXpC,EAAE,2DAA2D;wBAC3DkC,OAAOR,KAAKM,SAAS;wBACrBG,SAAST,KAAKK,WAAW;oBAC3B;gBAEJ;gBAEA,0CAA0C;gBAC1C,KAAK,MAAMnB,MAAMP,YAAa;oBAC5BD,aAAaQ;gBACf;gBAEAC,OAAOwB,OAAO;YAChB,EAAE,OAAOZ,OAAO;gBACda,QAAQb,KAAK,CAAC,8BAA8BA;gBAC5ClC,MAAMkC,KAAK,CAACzB,EAAE;YAChB;QACF;IACF;IAEA,OACEK,YAAYwB,MAAM,GAAG,mBACnB,KAACU;QAAIC,WAAU;QAAMC,OAAO;YAAEC,SAAS;YAAQC,gBAAgB;QAAQ;kBACrE,cAAA,MAACrD;YACCkD,WAAU;YACVI,UAAU3C,aAAaI,YAAYwB,MAAM,KAAK;YAC9CgB,MAAM5C,0BAAY,KAACJ,6BAAa,KAACD;YACjCkD,SAAShC;;gBAERd,EAAE;gBAA4D;gBAAEK,YAAYwB,MAAM;gBAAE;gBACpFxB,YAAYwB,MAAM,KAAK,IACpB7B,EAAE,iDACFA,EAAE;;;;AAKhB"}
|
|
@@ -4,7 +4,7 @@ import { Button, toast, useDocumentInfo, useField, useLocale, useTranslation } f
|
|
|
4
4
|
import { useTransition } from 'react';
|
|
5
5
|
import { Lightning } from './icons/Lightning.js';
|
|
6
6
|
import { Spinner } from './icons/Spinner.js';
|
|
7
|
-
export function GenerateAltTextButton() {
|
|
7
|
+
export function GenerateAltTextButton({ supportedMimeTypes }) {
|
|
8
8
|
const { t } = useTranslation();
|
|
9
9
|
const { id, collectionSlug } = useDocumentInfo();
|
|
10
10
|
const locale = useLocale();
|
|
@@ -15,6 +15,10 @@ export function GenerateAltTextButton() {
|
|
|
15
15
|
const { setValue: setAltText } = useField({
|
|
16
16
|
path: 'alt'
|
|
17
17
|
});
|
|
18
|
+
const { value: mimeType } = useField({
|
|
19
|
+
path: 'mimeType'
|
|
20
|
+
});
|
|
21
|
+
const isUnsupportedMimeType = !!mimeType && !!supportedMimeTypes && !supportedMimeTypes.includes(mimeType);
|
|
18
22
|
const handleGenerateAltText = ()=>{
|
|
19
23
|
if (!collectionSlug || !id) {
|
|
20
24
|
toast.error(t('@jhb.software/payload-alt-text-plugin:cannotGenerateMissingFields'));
|
|
@@ -96,10 +100,12 @@ export function GenerateAltTextButton() {
|
|
|
96
100
|
display: 'flex'
|
|
97
101
|
},
|
|
98
102
|
children: /*#__PURE__*/ _jsx(Button, {
|
|
99
|
-
disabled: isPending || !id,
|
|
103
|
+
disabled: isPending || !id || isUnsupportedMimeType,
|
|
100
104
|
icon: isPending ? /*#__PURE__*/ _jsx(Spinner, {}) : /*#__PURE__*/ _jsx(Lightning, {}),
|
|
101
105
|
onClick: handleGenerateAltText,
|
|
102
|
-
tooltip:
|
|
106
|
+
tooltip: isUnsupportedMimeType ? t('@jhb.software/payload-alt-text-plugin:unsupportedMimeType', {
|
|
107
|
+
mimeType
|
|
108
|
+
}) : !id ? t('@jhb.software/payload-alt-text-plugin:pleaseSaveDocumentFirst') : undefined,
|
|
103
109
|
children: t('@jhb.software/payload-alt-text-plugin:generateAltText')
|
|
104
110
|
})
|
|
105
111
|
})
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/GenerateAltTextButton.tsx"],"sourcesContent":["'use client'\n\nimport { Button, toast, useDocumentInfo, useField, useLocale, useTranslation } 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() {\n const { t } = useTranslation<PluginAltTextTranslations, PluginAltTextTranslationKeys>()\n const { id, collectionSlug } = useDocumentInfo()\n const locale = useLocale()\n const [isPending, startTransition] = useTransition()\n\n const { setValue: setKeywords } = useField<string>({ path: 'keywords' })\n const { setValue: setAltText } = useField<string>({ path: 'alt' })\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('/api/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}\n icon={isPending ? <Spinner /> : <Lightning />}\n onClick={handleGenerateAltText}\n tooltip={\n !id
|
|
1
|
+
{"version":3,"sources":["../../src/components/GenerateAltTextButton.tsx"],"sourcesContent":["'use client'\n\nimport { Button, toast, useDocumentInfo, useField, useLocale, useTranslation } 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\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('/api/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","useDocumentInfo","useField","useLocale","useTranslation","useTransition","Lightning","Spinner","GenerateAltTextButton","supportedMimeTypes","t","id","collectionSlug","locale","isPending","startTransition","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":"AAAA;;AAEA,SAASA,MAAM,EAAEC,KAAK,EAAEC,eAAe,EAAEC,QAAQ,EAAEC,SAAS,EAAEC,cAAc,QAAQ,iBAAgB;AACpG,SAASC,aAAa,QAAQ,QAAO;AAOrC,SAASC,SAAS,QAAQ,uBAAsB;AAChD,SAASC,OAAO,QAAQ,qBAAoB;AAE5C,OAAO,SAASC,sBAAsB,EAAEC,kBAAkB,EAAqC;IAC7F,MAAM,EAAEC,CAAC,EAAE,GAAGN;IACd,MAAM,EAAEO,EAAE,EAAEC,cAAc,EAAE,GAAGX;IAC/B,MAAMY,SAASV;IACf,MAAM,CAACW,WAAWC,gBAAgB,GAAGV;IAErC,MAAM,EAAEW,UAAUC,WAAW,EAAE,GAAGf,SAAiB;QAAEgB,MAAM;IAAW;IACtE,MAAM,EAAEF,UAAUG,UAAU,EAAE,GAAGjB,SAAiB;QAAEgB,MAAM;IAAM;IAChE,MAAM,EAAEE,OAAOC,QAAQ,EAAE,GAAGnB,SAAiB;QAAEgB,MAAM;IAAW;IAEhE,MAAMI,wBACJ,CAAC,CAACD,YAAY,CAAC,CAACZ,sBAAsB,CAACA,mBAAmBc,QAAQ,CAACF;IAErE,MAAMG,wBAAwB;QAC5B,IAAI,CAACZ,kBAAkB,CAACD,IAAI;YAC1BX,MAAMyB,KAAK,CAACf,EAAE;YACd,MAAM,IAAIgB,MAAM;QAClB;QAEAX,gBAAgB;YACd,IAAI;gBACF,MAAMY,WAAW,MAAMC,MAAM,iCAAiC;oBAC5DC,MAAMC,KAAKC,SAAS,CAAC;wBACnBpB,IAAIA;wBACJqB,YAAYpB;wBACZC,QAAQA,QAAQoB,QAAQ;oBAC1B;oBACAC,QAAQ;gBACV;gBAEA,IAAI,CAACP,SAASQ,EAAE,EAAE;oBAChB,IAAIC,eAAe1B,EAAE;oBACrB,IAAI;wBACF,MAAM2B,YAAa,MAAMV,SAASW,IAAI;wBACtCF,eAAeC,UAAUZ,KAAK;oBAChC,EAAE,OAAOA,OAAO;wBACdc,QAAQd,KAAK,CAAC,8BAA8BA;oBAC9C;oBAEAzB,MAAMyB,KAAK,CAACW;oBACZ;gBACF;gBAEA,MAAMI,OAAQ,MAAMb,SAASW,IAAI;gBAKjC,IAAIE,KAAKC,OAAO,IAAID,KAAKE,QAAQ,EAAE;oBACjCvB,WAAWqB,KAAKC,OAAO;oBACvBxB,YAAYuB,KAAKE,QAAQ;oBACzB1C,MAAM2C,OAAO,CAACjC,EAAE;gBAClB,OAAO;oBACLV,MAAMyB,KAAK,CAACf,EAAE;gBAChB;YACF,EAAE,OAAOe,OAAO;gBACdc,QAAQd,KAAK,CAAC,8BAA8BA;gBAC5CzB,MAAMyB,KAAK,CAACf,EAAE;YAChB;QACF;IACF;IAEA,qBACE,MAACkC;QAAIC,OAAO;YAAEC,SAAS;YAAQC,KAAK;YAAQC,WAAW;QAAO;;0BAC5D,MAACJ;gBAAIC,OAAO;oBAAEI,OAAO;oBAA8BC,MAAM;gBAAI;;kCAC3D,KAACC;kCAAGzC,EAAE;;kCACN,MAAC0C;wBAAGP,OAAO;4BAAEQ,QAAQ;4BAAUC,aAAa;wBAAO;;0CACjD,KAACC;0CAAI7C,EAAE;;0CACP,KAAC6C;0CAAI7C,EAAE;;0CACP,KAAC6C;0CAAI7C,EAAE;;;;;;0BAGX,KAACkC;gBAAIC,OAAO;oBAAEW,YAAY;oBAAUV,SAAS;gBAAO;0BAClD,cAAA,KAAC/C;oBACC0D,UAAU3C,aAAa,CAACH,MAAMW;oBAC9BoC,MAAM5C,0BAAY,KAACP,6BAAa,KAACD;oBACjCqD,SAASnC;oBACToC,SACEtC,wBACIZ,EAAE,6DAA6D;wBAAEW;oBAAS,KAC1E,CAACV,KACCD,EAAE,mEACFmD;8BAGPnD,EAAE;;;;;AAKb"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import React from 'react';
|
|
3
|
+
export const ArrowRightIcon = (props)=>/*#__PURE__*/ _jsx("svg", {
|
|
4
|
+
fill: "none",
|
|
5
|
+
height: "16",
|
|
6
|
+
shapeRendering: "geometricPrecision",
|
|
7
|
+
stroke: "currentColor",
|
|
8
|
+
strokeLinecap: "round",
|
|
9
|
+
strokeLinejoin: "round",
|
|
10
|
+
strokeWidth: "1.5",
|
|
11
|
+
style: {
|
|
12
|
+
color: 'currentcolor'
|
|
13
|
+
},
|
|
14
|
+
viewBox: "0 0 24 24",
|
|
15
|
+
width: "16",
|
|
16
|
+
...props,
|
|
17
|
+
children: /*#__PURE__*/ _jsx("path", {
|
|
18
|
+
d: "M5 12h14M12 5l7 7-7 7"
|
|
19
|
+
})
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
//# sourceMappingURL=ArrowRightIcon.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/components/icons/ArrowRightIcon.tsx"],"sourcesContent":["import React from 'react'\n\nexport const ArrowRightIcon: React.FC<React.SVGProps<SVGSVGElement>> = (props) => (\n <svg\n fill=\"none\"\n height=\"16\"\n shapeRendering=\"geometricPrecision\"\n stroke=\"currentColor\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth=\"1.5\"\n style={{ color: 'currentcolor' }}\n viewBox=\"0 0 24 24\"\n width=\"16\"\n {...props}\n >\n <path d=\"M5 12h14M12 5l7 7-7 7\" />\n </svg>\n)\n"],"names":["React","ArrowRightIcon","props","svg","fill","height","shapeRendering","stroke","strokeLinecap","strokeLinejoin","strokeWidth","style","color","viewBox","width","path","d"],"mappings":";AAAA,OAAOA,WAAW,QAAO;AAEzB,OAAO,MAAMC,iBAA0D,CAACC,sBACtE,KAACC;QACCC,MAAK;QACLC,QAAO;QACPC,gBAAe;QACfC,QAAO;QACPC,eAAc;QACdC,gBAAe;QACfC,aAAY;QACZC,OAAO;YAAEC,OAAO;QAAe;QAC/BC,SAAQ;QACRC,OAAM;QACL,GAAGZ,KAAK;kBAET,cAAA,KAACa;YAAKC,GAAE;;OAEX"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import React from 'react';
|
|
3
|
+
export const CheckIcon = (props)=>/*#__PURE__*/ _jsx("svg", {
|
|
4
|
+
fill: "none",
|
|
5
|
+
height: "16",
|
|
6
|
+
shapeRendering: "geometricPrecision",
|
|
7
|
+
stroke: "currentColor",
|
|
8
|
+
strokeLinecap: "round",
|
|
9
|
+
strokeLinejoin: "round",
|
|
10
|
+
strokeWidth: "1.5",
|
|
11
|
+
style: {
|
|
12
|
+
color: 'currentcolor'
|
|
13
|
+
},
|
|
14
|
+
viewBox: "0 0 24 24",
|
|
15
|
+
width: "16",
|
|
16
|
+
...props,
|
|
17
|
+
children: /*#__PURE__*/ _jsx("path", {
|
|
18
|
+
d: "M20 6L9 17l-5-5"
|
|
19
|
+
})
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
//# sourceMappingURL=CheckIcon.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/components/icons/CheckIcon.tsx"],"sourcesContent":["import React from 'react'\n\nexport const CheckIcon: React.FC<React.SVGProps<SVGSVGElement>> = (props) => (\n <svg\n fill=\"none\"\n height=\"16\"\n shapeRendering=\"geometricPrecision\"\n stroke=\"currentColor\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth=\"1.5\"\n style={{ color: 'currentcolor' }}\n viewBox=\"0 0 24 24\"\n width=\"16\"\n {...props}\n >\n <path d=\"M20 6L9 17l-5-5\" />\n </svg>\n)\n"],"names":["React","CheckIcon","props","svg","fill","height","shapeRendering","stroke","strokeLinecap","strokeLinejoin","strokeWidth","style","color","viewBox","width","path","d"],"mappings":";AAAA,OAAOA,WAAW,QAAO;AAEzB,OAAO,MAAMC,YAAqD,CAACC,sBACjE,KAACC;QACCC,MAAK;QACLC,QAAO;QACPC,gBAAe;QACfC,QAAO;QACPC,eAAc;QACdC,gBAAe;QACfC,aAAY;QACZC,OAAO;YAAEC,OAAO;QAAe;QAC/BC,SAAQ;QACRC,OAAM;QACL,GAAGZ,KAAK;kBAET,cAAA,KAACa;YAAKC,GAAE;;OAEX"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import React from 'react';
|
|
3
|
+
export const ImageIcon = (props)=>/*#__PURE__*/ _jsxs("svg", {
|
|
4
|
+
"data-testid": "geist-icon",
|
|
5
|
+
fill: "none",
|
|
6
|
+
height: "16",
|
|
7
|
+
shapeRendering: "geometricPrecision",
|
|
8
|
+
stroke: "currentColor",
|
|
9
|
+
strokeLinecap: "round",
|
|
10
|
+
strokeLinejoin: "round",
|
|
11
|
+
strokeWidth: "1.5",
|
|
12
|
+
style: {
|
|
13
|
+
color: 'currentcolor'
|
|
14
|
+
},
|
|
15
|
+
viewBox: "0 0 24 24",
|
|
16
|
+
width: "16",
|
|
17
|
+
...props,
|
|
18
|
+
children: [
|
|
19
|
+
/*#__PURE__*/ _jsx("rect", {
|
|
20
|
+
height: "18",
|
|
21
|
+
rx: "2",
|
|
22
|
+
ry: "2",
|
|
23
|
+
width: "18",
|
|
24
|
+
x: "3",
|
|
25
|
+
y: "3"
|
|
26
|
+
}),
|
|
27
|
+
/*#__PURE__*/ _jsx("circle", {
|
|
28
|
+
cx: "8.5",
|
|
29
|
+
cy: "8.5",
|
|
30
|
+
r: "1.5"
|
|
31
|
+
}),
|
|
32
|
+
/*#__PURE__*/ _jsx("path", {
|
|
33
|
+
d: "M21 15l-5-5L5 21"
|
|
34
|
+
})
|
|
35
|
+
]
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
//# sourceMappingURL=ImageIcon.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/components/icons/ImageIcon.tsx"],"sourcesContent":["import React from 'react'\n\nexport const ImageIcon: React.FC<React.SVGProps<SVGSVGElement>> = (props) => (\n <svg\n data-testid=\"geist-icon\"\n fill=\"none\"\n height=\"16\"\n shapeRendering=\"geometricPrecision\"\n stroke=\"currentColor\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth=\"1.5\"\n style={{ color: 'currentcolor' }}\n viewBox=\"0 0 24 24\"\n width=\"16\"\n {...props}\n >\n <rect height=\"18\" rx=\"2\" ry=\"2\" width=\"18\" x=\"3\" y=\"3\" />\n <circle cx=\"8.5\" cy=\"8.5\" r=\"1.5\" />\n <path d=\"M21 15l-5-5L5 21\" />\n </svg>\n)\n"],"names":["React","ImageIcon","props","svg","data-testid","fill","height","shapeRendering","stroke","strokeLinecap","strokeLinejoin","strokeWidth","style","color","viewBox","width","rect","rx","ry","x","y","circle","cx","cy","r","path","d"],"mappings":";AAAA,OAAOA,WAAW,QAAO;AAEzB,OAAO,MAAMC,YAAqD,CAACC,sBACjE,MAACC;QACCC,eAAY;QACZC,MAAK;QACLC,QAAO;QACPC,gBAAe;QACfC,QAAO;QACPC,eAAc;QACdC,gBAAe;QACfC,aAAY;QACZC,OAAO;YAAEC,OAAO;QAAe;QAC/BC,SAAQ;QACRC,OAAM;QACL,GAAGb,KAAK;;0BAET,KAACc;gBAAKV,QAAO;gBAAKW,IAAG;gBAAIC,IAAG;gBAAIH,OAAM;gBAAKI,GAAE;gBAAIC,GAAE;;0BACnD,KAACC;gBAAOC,IAAG;gBAAMC,IAAG;gBAAMC,GAAE;;0BAC5B,KAACC;gBAAKC,GAAE;;;OAEX"}
|
|
@@ -110,6 +110,10 @@ async function generateAndUpdateAltText({ id, collection, locales, payload, plug
|
|
|
110
110
|
if (!imageDoc) {
|
|
111
111
|
throw new Error('Image not found');
|
|
112
112
|
}
|
|
113
|
+
const mimeType = 'mimeType' in imageDoc && typeof imageDoc.mimeType === 'string' ? imageDoc.mimeType : undefined;
|
|
114
|
+
if (mimeType && pluginConfig.resolver.supportedMimeTypes && !pluginConfig.resolver.supportedMimeTypes.includes(mimeType)) {
|
|
115
|
+
throw new Error(`Alt text generation is not supported for files of type "${mimeType}". Supported types: ${pluginConfig.resolver.supportedMimeTypes.join(', ')}.`);
|
|
116
|
+
}
|
|
113
117
|
const imageThumbnailUrl = pluginConfig.getImageThumbnail(imageDoc);
|
|
114
118
|
const result = await pluginConfig.resolver.resolveBulk({
|
|
115
119
|
filename: 'filename' in imageDoc && typeof imageDoc.filename === 'string' ? imageDoc.filename : undefined,
|
|
@@ -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 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","imageThumbnailUrl","getImageThumbnail","result","resolveBulk","filename","
|
|
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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"}
|
|
@@ -63,6 +63,14 @@ import { z, ZodError } from 'zod';
|
|
|
63
63
|
status: 500
|
|
64
64
|
});
|
|
65
65
|
}
|
|
66
|
+
const mimeType = 'mimeType' in imageDoc && typeof imageDoc.mimeType === 'string' ? imageDoc.mimeType : undefined;
|
|
67
|
+
if (mimeType && pluginConfig.resolver.supportedMimeTypes && !pluginConfig.resolver.supportedMimeTypes.includes(mimeType)) {
|
|
68
|
+
return Response.json({
|
|
69
|
+
error: `Alt text generation is not supported for files of type "${mimeType}". Supported types: ${pluginConfig.resolver.supportedMimeTypes.join(', ')}.`
|
|
70
|
+
}, {
|
|
71
|
+
status: 400
|
|
72
|
+
});
|
|
73
|
+
}
|
|
66
74
|
// determine target locale
|
|
67
75
|
const targetLocale = locale ?? pluginConfig.locale;
|
|
68
76
|
if (!targetLocale) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/endpoints/generateAltText.ts"],"sourcesContent":["import type { PayloadHandler, PayloadRequest } from 'payload'\n\nimport { z, ZodError } from 'zod'\n\nimport type { AltTextPluginConfig } from '../types/AltTextPluginConfig.js'\n\n/**\n * Generates alt text for a single image using the configured resolver.\n *\n * By default, returns the result without updating the document (preview mode).\n * Pass `update: true` in the request body to also persist the generated alt text\n * and keywords to the document — useful for programmatic/agent workflows.\n *\n * The response always includes the `id` and `collection` for easy correlation.\n */\nexport const generateAltTextEndpoint =\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 requestSchema = z.object({\n id: z.union([z.string(), z.number()]),\n collection: z.string(),\n locale: z.string().nullable(),\n update: z.boolean().optional().default(false),\n })\n\n const { id, collection, locale, update } = requestSchema.parse(data)\n\n const imageDoc = await req.payload.findByID({\n id,\n collection,\n depth: 0,\n })\n\n if (!imageDoc) {\n return Response.json({ error: 'Image not found' }, { status: 404 })\n }\n\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.getImageThumbnail) {\n return Response.json(\n { error: 'getImageThumbnail function not configured' },\n { status: 500 },\n )\n }\n\n if (!pluginConfig.resolver) {\n return Response.json({ error: 'No alt text resolver configured' }, { status: 500 })\n }\n\n // determine target locale\n const targetLocale = locale ?? pluginConfig.locale\n if (!targetLocale) {\n return Response.json(\n {\n error:\n 'Could not determine target locale for alt text generation. Please check your plugin configuration.',\n },\n { status: 500 },\n )\n }\n\n const imageThumbnailUrl = pluginConfig.getImageThumbnail(imageDoc)\n\n const result = await pluginConfig.resolver.resolve({\n filename:\n 'filename' in imageDoc && typeof imageDoc.filename === 'string'\n ? imageDoc.filename\n : undefined,\n imageThumbnailUrl,\n locale: targetLocale,\n req,\n })\n\n if (!result.success) {\n return Response.json(\n { error: result.error || 'Failed to generate alt text' },\n { status: 500 },\n )\n }\n\n if (update) {\n await req.payload.update({\n id,\n collection,\n data: {\n alt: result.result.altText,\n keywords: result.result.keywords,\n },\n locale: targetLocale,\n })\n }\n\n return Response.json({ id, collection, ...result.result })\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 generating alt text:', 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"],"names":["z","ZodError","generateAltTextEndpoint","access","req","Response","json","error","status","data","requestSchema","object","id","union","string","number","collection","locale","nullable","update","boolean","optional","default","parse","imageDoc","payload","findByID","depth","pluginConfig","config","custom","altTextPluginConfig","getImageThumbnail","resolver","targetLocale","imageThumbnailUrl","result","resolve","filename","
|
|
1
|
+
{"version":3,"sources":["../../src/endpoints/generateAltText.ts"],"sourcesContent":["import type { PayloadHandler, PayloadRequest } from 'payload'\n\nimport { z, ZodError } from 'zod'\n\nimport type { AltTextPluginConfig } from '../types/AltTextPluginConfig.js'\n\n/**\n * Generates alt text for a single image using the configured resolver.\n *\n * By default, returns the result without updating the document (preview mode).\n * Pass `update: true` in the request body to also persist the generated alt text\n * and keywords to the document — useful for programmatic/agent workflows.\n *\n * The response always includes the `id` and `collection` for easy correlation.\n */\nexport const generateAltTextEndpoint =\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 requestSchema = z.object({\n id: z.union([z.string(), z.number()]),\n collection: z.string(),\n locale: z.string().nullable(),\n update: z.boolean().optional().default(false),\n })\n\n const { id, collection, locale, update } = requestSchema.parse(data)\n\n const imageDoc = await req.payload.findByID({\n id,\n collection,\n depth: 0,\n })\n\n if (!imageDoc) {\n return Response.json({ error: 'Image not found' }, { status: 404 })\n }\n\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.getImageThumbnail) {\n return Response.json(\n { error: 'getImageThumbnail function not configured' },\n { status: 500 },\n )\n }\n\n if (!pluginConfig.resolver) {\n return Response.json({ error: 'No alt text resolver configured' }, { status: 500 })\n }\n\n const mimeType =\n 'mimeType' in imageDoc && typeof imageDoc.mimeType === 'string'\n ? imageDoc.mimeType\n : undefined\n\n if (\n mimeType &&\n pluginConfig.resolver.supportedMimeTypes &&\n !pluginConfig.resolver.supportedMimeTypes.includes(mimeType)\n ) {\n return Response.json(\n {\n error: `Alt text generation is not supported for files of type \"${mimeType}\". Supported types: ${pluginConfig.resolver.supportedMimeTypes.join(', ')}.`,\n },\n { status: 400 },\n )\n }\n\n // determine target locale\n const targetLocale = locale ?? pluginConfig.locale\n if (!targetLocale) {\n return Response.json(\n {\n error:\n 'Could not determine target locale for alt text generation. Please check your plugin configuration.',\n },\n { status: 500 },\n )\n }\n\n const imageThumbnailUrl = pluginConfig.getImageThumbnail(imageDoc)\n\n const result = await pluginConfig.resolver.resolve({\n filename:\n 'filename' in imageDoc && typeof imageDoc.filename === 'string'\n ? imageDoc.filename\n : undefined,\n imageThumbnailUrl,\n locale: targetLocale,\n req,\n })\n\n if (!result.success) {\n return Response.json(\n { error: result.error || 'Failed to generate alt text' },\n { status: 500 },\n )\n }\n\n if (update) {\n await req.payload.update({\n id,\n collection,\n data: {\n alt: result.result.altText,\n keywords: result.result.keywords,\n },\n locale: targetLocale,\n })\n }\n\n return Response.json({ id, collection, ...result.result })\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 generating alt text:', 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"],"names":["z","ZodError","generateAltTextEndpoint","access","req","Response","json","error","status","data","requestSchema","object","id","union","string","number","collection","locale","nullable","update","boolean","optional","default","parse","imageDoc","payload","findByID","depth","pluginConfig","config","custom","altTextPluginConfig","getImageThumbnail","resolver","mimeType","undefined","supportedMimeTypes","includes","join","targetLocale","imageThumbnailUrl","result","resolve","filename","success","alt","altText","keywords","details","issues","map","e","message","path","console","Error"],"mappings":"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"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { TextareaField } from 'payload';
|
|
2
|
-
export declare function altTextField({ localized, }: {
|
|
2
|
+
export declare function altTextField({ localized, supportedMimeTypes, }: {
|
|
3
3
|
localized?: TextareaField['localized'];
|
|
4
|
+
supportedMimeTypes?: string[];
|
|
4
5
|
}): TextareaField;
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import { translatedLabel } from '../utils/translatedLabel.js';
|
|
2
|
-
export function altTextField({ localized }) {
|
|
2
|
+
export function altTextField({ localized, supportedMimeTypes }) {
|
|
3
3
|
return {
|
|
4
4
|
name: 'alt',
|
|
5
5
|
type: 'textarea',
|
|
6
6
|
admin: {
|
|
7
7
|
components: {
|
|
8
8
|
Field: '@jhb.software/payload-alt-text-plugin/client#AltTextField'
|
|
9
|
+
},
|
|
10
|
+
custom: {
|
|
11
|
+
supportedMimeTypes
|
|
9
12
|
}
|
|
10
13
|
},
|
|
11
14
|
label: translatedLabel('alternateText'),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/fields/altTextField.ts"],"sourcesContent":["import type { TextareaField } from 'payload'\n\nimport { translatedLabel } from '../utils/translatedLabel.js'\n\nexport function altTextField({\n localized,\n}: {\n localized?: TextareaField['localized']\n}): TextareaField {\n return {\n name: 'alt',\n type: 'textarea',\n admin: {\n components: {\n Field: '@jhb.software/payload-alt-text-plugin/client#AltTextField',\n },\n },\n label: translatedLabel('alternateText'),\n localized,\n required: true,\n validate: (value, { data, operation, req: { t } }) => {\n // Since https://github.com/payloadcms/payload/pull/14988, when using external storage (e.g., S3),\n // it is no longer possible to detect whether this validation runs during the initial upload\n // or a regular update by checking the existence of the ID.\n // Instead, compare the timestamps of the createdAt and updatedAt fields.\n const isInitialUpload =\n operation === 'create' ||\n ('createdAt' in data && 'updatedAt' in data && data.createdAt === data.updatedAt)\n\n // initial upload: allow without alt text\n if (isInitialUpload) {\n return true\n }\n\n // regular update: require alt text\n if (!value || value.trim().length === 0) {\n // @ts-expect-error - the translation key type does not include the custom key\n return t('@jhb.software/payload-alt-text-plugin:theAlternateTextIsRequired')\n }\n\n return true\n },\n }\n}\n"],"names":["translatedLabel","altTextField","localized","name","type","admin","components","Field","label","required","validate","value","data","operation","req","t","isInitialUpload","createdAt","updatedAt","trim","length"],"mappings":"AAEA,SAASA,eAAe,QAAQ,8BAA6B;AAE7D,OAAO,SAASC,aAAa,EAC3BC,SAAS,
|
|
1
|
+
{"version":3,"sources":["../../src/fields/altTextField.ts"],"sourcesContent":["import type { TextareaField } from 'payload'\n\nimport { translatedLabel } from '../utils/translatedLabel.js'\n\nexport function altTextField({\n localized,\n supportedMimeTypes,\n}: {\n localized?: TextareaField['localized']\n supportedMimeTypes?: string[]\n}): TextareaField {\n return {\n name: 'alt',\n type: 'textarea',\n admin: {\n components: {\n Field: '@jhb.software/payload-alt-text-plugin/client#AltTextField',\n },\n custom: {\n supportedMimeTypes,\n },\n },\n label: translatedLabel('alternateText'),\n localized,\n required: true,\n validate: (value, { data, operation, req: { t } }) => {\n // Since https://github.com/payloadcms/payload/pull/14988, when using external storage (e.g., S3),\n // it is no longer possible to detect whether this validation runs during the initial upload\n // or a regular update by checking the existence of the ID.\n // Instead, compare the timestamps of the createdAt and updatedAt fields.\n const isInitialUpload =\n operation === 'create' ||\n ('createdAt' in data && 'updatedAt' in data && data.createdAt === data.updatedAt)\n\n // initial upload: allow without alt text\n if (isInitialUpload) {\n return true\n }\n\n // regular update: require alt text\n if (!value || value.trim().length === 0) {\n // @ts-expect-error - the translation key type does not include the custom key\n return t('@jhb.software/payload-alt-text-plugin:theAlternateTextIsRequired')\n }\n\n return true\n },\n }\n}\n"],"names":["translatedLabel","altTextField","localized","supportedMimeTypes","name","type","admin","components","Field","custom","label","required","validate","value","data","operation","req","t","isInitialUpload","createdAt","updatedAt","trim","length"],"mappings":"AAEA,SAASA,eAAe,QAAQ,8BAA6B;AAE7D,OAAO,SAASC,aAAa,EAC3BC,SAAS,EACTC,kBAAkB,EAInB;IACC,OAAO;QACLC,MAAM;QACNC,MAAM;QACNC,OAAO;YACLC,YAAY;gBACVC,OAAO;YACT;YACAC,QAAQ;gBACNN;YACF;QACF;QACAO,OAAOV,gBAAgB;QACvBE;QACAS,UAAU;QACVC,UAAU,CAACC,OAAO,EAAEC,IAAI,EAAEC,SAAS,EAAEC,KAAK,EAAEC,CAAC,EAAE,EAAE;YAC/C,kGAAkG;YAClG,4FAA4F;YAC5F,2DAA2D;YAC3D,yEAAyE;YACzE,MAAMC,kBACJH,cAAc,YACb,eAAeD,QAAQ,eAAeA,QAAQA,KAAKK,SAAS,KAAKL,KAAKM,SAAS;YAElF,yCAAyC;YACzC,IAAIF,iBAAiB;gBACnB,OAAO;YACT;YAEA,mCAAmC;YACnC,IAAI,CAACL,SAASA,MAAMQ,IAAI,GAAGC,MAAM,KAAK,GAAG;gBACvC,8EAA8E;gBAC9E,OAAOL,EAAE;YACX;YAEA,OAAO;QACT;IACF;AACF"}
|
package/dist/plugin.js
CHANGED
|
@@ -76,7 +76,8 @@ export const payloadAltTextPlugin = (incomingPluginConfig)=>(incomingConfig)=>{
|
|
|
76
76
|
}
|
|
77
77
|
const defaultFields = [
|
|
78
78
|
altTextField({
|
|
79
|
-
localized: Boolean(config.localization)
|
|
79
|
+
localized: Boolean(config.localization),
|
|
80
|
+
supportedMimeTypes: pluginConfig.resolver.supportedMimeTypes
|
|
80
81
|
}),
|
|
81
82
|
keywordsField({
|
|
82
83
|
localized: Boolean(config.localization)
|
package/dist/plugin.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/plugin.ts"],"sourcesContent":["import type { Config, PayloadRequest, Widget, WidgetInstance } from 'payload'\n\nimport type {\n AltTextPluginConfig,\n IncomingAltTextPluginConfig,\n} from './types/AltTextPluginConfig.js'\n\nimport { altTextHealthEndpoint } from './endpoints/altTextHealth.js'\nimport { bulkGenerateAltTextsEndpoint } from './endpoints/bulkGenerateAltTexts.js'\nimport { generateAltTextEndpoint } from './endpoints/generateAltText.js'\nimport { altTextField } from './fields/altTextField.js'\nimport { keywordsField } from './fields/keywordsField.js'\nimport {\n createRevalidateAltTextHealthAfterChangeHook,\n createRevalidateAltTextHealthAfterDeleteHook,\n} from './hooks/revalidateAltTextHealth.js'\nimport { translations } from './translations/index.js'\nimport { deepMergeSimple } from './utils/deepMergeSimple.js'\n\nconst altTextHealthWidgetDefinition = {\n slug: 'alt-text-health',\n // `Component` was renamed from `ComponentPath` in Payload 3.79.0. Set both for backward compatibility.\n Component: '@jhb.software/payload-alt-text-plugin/server#AltTextHealthWidget',\n ComponentPath: '@jhb.software/payload-alt-text-plugin/server#AltTextHealthWidget',\n label: {\n de: 'Alternativtexte Zustand',\n en: 'Alt text health',\n },\n maxWidth: 'full',\n minWidth: 'medium',\n} satisfies { ComponentPath: string } & Widget\n\ntype DashboardDefaultLayout = Config['admin'] extends infer TAdmin\n ? TAdmin extends { dashboard?: infer TDashboard }\n ? TDashboard extends { defaultLayout?: infer TDefaultLayout }\n ? TDefaultLayout\n : never\n : never\n : never\n\nconst defaultAltTextHealthWidgetLayout: WidgetInstance = {\n widgetSlug: 'alt-text-health',\n width: 'full',\n}\n\nfunction appendAltTextHealthWidgetToLayout(layout: WidgetInstance[]): WidgetInstance[] {\n if (layout.some((widget) => widget.widgetSlug === 'alt-text-health')) {\n return layout\n }\n\n return [...layout, defaultAltTextHealthWidgetLayout]\n}\n\nfunction getDashboardDefaultLayout(defaultLayout: DashboardDefaultLayout | undefined) {\n if (!defaultLayout) {\n return [\n {\n widgetSlug: 'collections',\n width: 'full',\n },\n defaultAltTextHealthWidgetLayout,\n ] satisfies WidgetInstance[]\n }\n\n if (Array.isArray(defaultLayout)) {\n return appendAltTextHealthWidgetToLayout(defaultLayout)\n }\n\n return async ({ req }: { req: PayloadRequest }) =>\n appendAltTextHealthWidgetToLayout(await defaultLayout({ req }))\n}\n\nexport const payloadAltTextPlugin =\n (incomingPluginConfig: IncomingAltTextPluginConfig) =>\n (incomingConfig: Config): Config => {\n const config = { ...incomingConfig }\n\n // If the plugin is disabled, return the config without modifying it\n if (incomingPluginConfig.enabled === false) {\n return config\n }\n\n const locales = config.localization\n ? config.localization.locales.map((localeConfig) =>\n typeof localeConfig === 'string' ? localeConfig : localeConfig.code,\n )\n : []\n\n const enableHealthCheck = incomingPluginConfig.healthCheck !== false\n\n const pluginConfig: AltTextPluginConfig = {\n access: incomingPluginConfig.access ?? (({ req }) => !!req.user),\n collections: incomingPluginConfig.collections,\n enabled: incomingPluginConfig.enabled ?? true,\n fieldsOverride: incomingPluginConfig.fieldsOverride,\n getImageThumbnail: incomingPluginConfig.getImageThumbnail,\n healthCheck: enableHealthCheck,\n locale: incomingPluginConfig.locale,\n locales,\n maxBulkGenerateConcurrency: incomingPluginConfig.maxBulkGenerateConcurrency ?? 16,\n resolver: incomingPluginConfig.resolver,\n }\n\n // Validate locale requirement for non-localized mode\n if (locales.length === 0 && !incomingPluginConfig.locale) {\n throw new Error(\n 'The alt-text plugin requires a \"locale\" option when Payload localization is disabled. ' +\n 'Please add { locale: \"en\" } (or your preferred locale) to your plugin configuration.',\n )\n }\n\n const defaultFields = [\n altTextField({\n localized: Boolean(config.localization),\n }),\n keywordsField({\n localized: Boolean(config.localization),\n }),\n ]\n\n const fields =\n incomingPluginConfig.fieldsOverride &&\n typeof incomingPluginConfig.fieldsOverride === 'function'\n ? incomingPluginConfig.fieldsOverride({ defaultFields })\n : defaultFields\n\n // Ensure collections array exists\n config.collections = config.collections || []\n\n // Map over collections and inject AI alt text fields into specified ones\n config.collections = config.collections.map((collectionConfig) => {\n if (pluginConfig.collections.includes(collectionConfig.slug)) {\n if (!collectionConfig.upload) {\n console.warn(\n `AI Alt Text Plugin: Collection \"${collectionConfig.slug}\" is not an upload collection. Skipping field injection.`,\n )\n return collectionConfig\n }\n\n return {\n ...collectionConfig,\n admin: {\n ...collectionConfig.admin,\n components: {\n ...(collectionConfig.admin?.components ?? {}),\n // TODO: use the beforeBulkAction custom component slot once available: https://github.com/payloadcms/payload/pull/11719\n beforeListTable: [\n ...(collectionConfig.admin?.components?.beforeListTable ?? []),\n {\n path: '@jhb.software/payload-alt-text-plugin/client#BulkGenerateAltTextsButton',\n props: {\n collectionSlug: collectionConfig.slug,\n },\n },\n ],\n },\n // enhance the search by adding the filename, keywords and alt fields (if the user has not provided their own listSearchableFields)\n listSearchableFields: collectionConfig.admin?.listSearchableFields ?? [\n 'filename',\n 'keywords',\n 'alt',\n ],\n },\n fields: [...(collectionConfig.fields ?? []), ...fields],\n hooks: {\n ...collectionConfig.hooks,\n ...(enableHealthCheck && {\n afterChange: [\n ...(collectionConfig.hooks?.afterChange ?? []),\n createRevalidateAltTextHealthAfterChangeHook(collectionConfig.slug),\n ],\n afterDelete: [\n ...(collectionConfig.hooks?.afterDelete ?? []),\n createRevalidateAltTextHealthAfterDeleteHook(collectionConfig.slug),\n ],\n }),\n },\n }\n }\n\n return collectionConfig\n })\n\n const existingWidgets = config.admin?.dashboard?.widgets ?? []\n const widgets =\n !enableHealthCheck || existingWidgets.some((widget) => widget.slug === 'alt-text-health')\n ? existingWidgets\n : [...existingWidgets, altTextHealthWidgetDefinition]\n\n return {\n ...config,\n admin: {\n ...config.admin,\n dashboard: {\n ...config.admin?.dashboard,\n ...(enableHealthCheck && {\n defaultLayout: getDashboardDefaultLayout(config.admin?.dashboard?.defaultLayout),\n }),\n widgets,\n },\n },\n custom: {\n ...config.custom,\n // Make plugin config available in hooks/actions\n altTextPluginConfig: pluginConfig,\n },\n endpoints: [\n ...(config.endpoints ?? []),\n {\n handler: generateAltTextEndpoint(pluginConfig.access),\n method: 'post',\n path: '/alt-text-plugin/generate',\n },\n {\n handler: bulkGenerateAltTextsEndpoint(pluginConfig.access),\n method: 'post',\n path: '/alt-text-plugin/generate/bulk',\n },\n ...(enableHealthCheck\n ? [\n {\n handler: altTextHealthEndpoint(pluginConfig.access),\n method: 'get' as const,\n path: '/alt-text-plugin/health',\n },\n ]\n : []),\n ],\n i18n: {\n ...config.i18n,\n translations: deepMergeSimple(translations, incomingConfig.i18n?.translations ?? {}),\n },\n }\n }\n"],"names":["altTextHealthEndpoint","bulkGenerateAltTextsEndpoint","generateAltTextEndpoint","altTextField","keywordsField","createRevalidateAltTextHealthAfterChangeHook","createRevalidateAltTextHealthAfterDeleteHook","translations","deepMergeSimple","altTextHealthWidgetDefinition","slug","Component","ComponentPath","label","de","en","maxWidth","minWidth","defaultAltTextHealthWidgetLayout","widgetSlug","width","appendAltTextHealthWidgetToLayout","layout","some","widget","getDashboardDefaultLayout","defaultLayout","Array","isArray","req","payloadAltTextPlugin","incomingPluginConfig","incomingConfig","config","enabled","locales","localization","map","localeConfig","code","enableHealthCheck","healthCheck","pluginConfig","access","user","collections","fieldsOverride","getImageThumbnail","locale","maxBulkGenerateConcurrency","resolver","length","Error","defaultFields","localized","Boolean","fields","collectionConfig","includes","upload","console","warn","admin","components","beforeListTable","path","props","collectionSlug","listSearchableFields","hooks","afterChange","afterDelete","existingWidgets","dashboard","widgets","custom","altTextPluginConfig","endpoints","handler","method","i18n"],"mappings":"AAOA,SAASA,qBAAqB,QAAQ,+BAA8B;AACpE,SAASC,4BAA4B,QAAQ,sCAAqC;AAClF,SAASC,uBAAuB,QAAQ,iCAAgC;AACxE,SAASC,YAAY,QAAQ,2BAA0B;AACvD,SAASC,aAAa,QAAQ,4BAA2B;AACzD,SACEC,4CAA4C,EAC5CC,4CAA4C,QACvC,qCAAoC;AAC3C,SAASC,YAAY,QAAQ,0BAAyB;AACtD,SAASC,eAAe,QAAQ,6BAA4B;AAE5D,MAAMC,gCAAgC;IACpCC,MAAM;IACN,uGAAuG;IACvGC,WAAW;IACXC,eAAe;IACfC,OAAO;QACLC,IAAI;QACJC,IAAI;IACN;IACAC,UAAU;IACVC,UAAU;AACZ;AAUA,MAAMC,mCAAmD;IACvDC,YAAY;IACZC,OAAO;AACT;AAEA,SAASC,kCAAkCC,MAAwB;IACjE,IAAIA,OAAOC,IAAI,CAAC,CAACC,SAAWA,OAAOL,UAAU,KAAK,oBAAoB;QACpE,OAAOG;IACT;IAEA,OAAO;WAAIA;QAAQJ;KAAiC;AACtD;AAEA,SAASO,0BAA0BC,aAAiD;IAClF,IAAI,CAACA,eAAe;QAClB,OAAO;YACL;gBACEP,YAAY;gBACZC,OAAO;YACT;YACAF;SACD;IACH;IAEA,IAAIS,MAAMC,OAAO,CAACF,gBAAgB;QAChC,OAAOL,kCAAkCK;IAC3C;IAEA,OAAO,OAAO,EAAEG,GAAG,EAA2B,GAC5CR,kCAAkC,MAAMK,cAAc;YAAEG;QAAI;AAChE;AAEA,OAAO,MAAMC,uBACX,CAACC,uBACD,CAACC;QACC,MAAMC,SAAS;YAAE,GAAGD,cAAc;QAAC;QAEnC,oEAAoE;QACpE,IAAID,qBAAqBG,OAAO,KAAK,OAAO;YAC1C,OAAOD;QACT;QAEA,MAAME,UAAUF,OAAOG,YAAY,GAC/BH,OAAOG,YAAY,CAACD,OAAO,CAACE,GAAG,CAAC,CAACC,eAC/B,OAAOA,iBAAiB,WAAWA,eAAeA,aAAaC,IAAI,IAErE,EAAE;QAEN,MAAMC,oBAAoBT,qBAAqBU,WAAW,KAAK;QAE/D,MAAMC,eAAoC;YACxCC,QAAQZ,qBAAqBY,MAAM,IAAK,CAAA,CAAC,EAAEd,GAAG,EAAE,GAAK,CAAC,CAACA,IAAIe,IAAI,AAAD;YAC9DC,aAAad,qBAAqBc,WAAW;YAC7CX,SAASH,qBAAqBG,OAAO,IAAI;YACzCY,gBAAgBf,qBAAqBe,cAAc;YACnDC,mBAAmBhB,qBAAqBgB,iBAAiB;YACzDN,aAAaD;YACbQ,QAAQjB,qBAAqBiB,MAAM;YACnCb;YACAc,4BAA4BlB,qBAAqBkB,0BAA0B,IAAI;YAC/EC,UAAUnB,qBAAqBmB,QAAQ;QACzC;QAEA,qDAAqD;QACrD,IAAIf,QAAQgB,MAAM,KAAK,KAAK,CAACpB,qBAAqBiB,MAAM,EAAE;YACxD,MAAM,IAAII,MACR,2FACE;QAEN;QAEA,MAAMC,gBAAgB;YACpBlD,aAAa;gBACXmD,WAAWC,QAAQtB,OAAOG,YAAY;YACxC;YACAhC,cAAc;gBACZkD,WAAWC,QAAQtB,OAAOG,YAAY;YACxC;SACD;QAED,MAAMoB,SACJzB,qBAAqBe,cAAc,IACnC,OAAOf,qBAAqBe,cAAc,KAAK,aAC3Cf,qBAAqBe,cAAc,CAAC;YAAEO;QAAc,KACpDA;QAEN,kCAAkC;QAClCpB,OAAOY,WAAW,GAAGZ,OAAOY,WAAW,IAAI,EAAE;QAE7C,yEAAyE;QACzEZ,OAAOY,WAAW,GAAGZ,OAAOY,WAAW,CAACR,GAAG,CAAC,CAACoB;YAC3C,IAAIf,aAAaG,WAAW,CAACa,QAAQ,CAACD,iBAAiB/C,IAAI,GAAG;gBAC5D,IAAI,CAAC+C,iBAAiBE,MAAM,EAAE;oBAC5BC,QAAQC,IAAI,CACV,CAAC,gCAAgC,EAAEJ,iBAAiB/C,IAAI,CAAC,wDAAwD,CAAC;oBAEpH,OAAO+C;gBACT;gBAEA,OAAO;oBACL,GAAGA,gBAAgB;oBACnBK,OAAO;wBACL,GAAGL,iBAAiBK,KAAK;wBACzBC,YAAY;4BACV,GAAIN,iBAAiBK,KAAK,EAAEC,cAAc,CAAC,CAAC;4BAC5C,wHAAwH;4BACxHC,iBAAiB;mCACXP,iBAAiBK,KAAK,EAAEC,YAAYC,mBAAmB,EAAE;gCAC7D;oCACEC,MAAM;oCACNC,OAAO;wCACLC,gBAAgBV,iBAAiB/C,IAAI;oCACvC;gCACF;6BACD;wBACH;wBACA,mIAAmI;wBACnI0D,sBAAsBX,iBAAiBK,KAAK,EAAEM,wBAAwB;4BACpE;4BACA;4BACA;yBACD;oBACH;oBACAZ,QAAQ;2BAAKC,iBAAiBD,MAAM,IAAI,EAAE;2BAAMA;qBAAO;oBACvDa,OAAO;wBACL,GAAGZ,iBAAiBY,KAAK;wBACzB,GAAI7B,qBAAqB;4BACvB8B,aAAa;mCACPb,iBAAiBY,KAAK,EAAEC,eAAe,EAAE;gCAC7CjE,6CAA6CoD,iBAAiB/C,IAAI;6BACnE;4BACD6D,aAAa;mCACPd,iBAAiBY,KAAK,EAAEE,eAAe,EAAE;gCAC7CjE,6CAA6CmD,iBAAiB/C,IAAI;6BACnE;wBACH,CAAC;oBACH;gBACF;YACF;YAEA,OAAO+C;QACT;QAEA,MAAMe,kBAAkBvC,OAAO6B,KAAK,EAAEW,WAAWC,WAAW,EAAE;QAC9D,MAAMA,UACJ,CAAClC,qBAAqBgC,gBAAgBjD,IAAI,CAAC,CAACC,SAAWA,OAAOd,IAAI,KAAK,qBACnE8D,kBACA;eAAIA;YAAiB/D;SAA8B;QAEzD,OAAO;YACL,GAAGwB,MAAM;YACT6B,OAAO;gBACL,GAAG7B,OAAO6B,KAAK;gBACfW,WAAW;oBACT,GAAGxC,OAAO6B,KAAK,EAAEW,SAAS;oBAC1B,GAAIjC,qBAAqB;wBACvBd,eAAeD,0BAA0BQ,OAAO6B,KAAK,EAAEW,WAAW/C;oBACpE,CAAC;oBACDgD;gBACF;YACF;YACAC,QAAQ;gBACN,GAAG1C,OAAO0C,MAAM;gBAChB,gDAAgD;gBAChDC,qBAAqBlC;YACvB;YACAmC,WAAW;mBACL5C,OAAO4C,SAAS,IAAI,EAAE;gBAC1B;oBACEC,SAAS5E,wBAAwBwC,aAAaC,MAAM;oBACpDoC,QAAQ;oBACRd,MAAM;gBACR;gBACA;oBACEa,SAAS7E,6BAA6ByC,aAAaC,MAAM;oBACzDoC,QAAQ;oBACRd,MAAM;gBACR;mBACIzB,oBACA;oBACE;wBACEsC,SAAS9E,sBAAsB0C,aAAaC,MAAM;wBAClDoC,QAAQ;wBACRd,MAAM;oBACR;iBACD,GACD,EAAE;aACP;YACDe,MAAM;gBACJ,GAAG/C,OAAO+C,IAAI;gBACdzE,cAAcC,gBAAgBD,cAAcyB,eAAegD,IAAI,EAAEzE,gBAAgB,CAAC;YACpF;QACF;IACF,EAAC"}
|
|
1
|
+
{"version":3,"sources":["../src/plugin.ts"],"sourcesContent":["import type { Config, PayloadRequest, Widget, WidgetInstance } from 'payload'\n\nimport type {\n AltTextPluginConfig,\n IncomingAltTextPluginConfig,\n} from './types/AltTextPluginConfig.js'\n\nimport { altTextHealthEndpoint } from './endpoints/altTextHealth.js'\nimport { bulkGenerateAltTextsEndpoint } from './endpoints/bulkGenerateAltTexts.js'\nimport { generateAltTextEndpoint } from './endpoints/generateAltText.js'\nimport { altTextField } from './fields/altTextField.js'\nimport { keywordsField } from './fields/keywordsField.js'\nimport {\n createRevalidateAltTextHealthAfterChangeHook,\n createRevalidateAltTextHealthAfterDeleteHook,\n} from './hooks/revalidateAltTextHealth.js'\nimport { translations } from './translations/index.js'\nimport { deepMergeSimple } from './utils/deepMergeSimple.js'\n\nconst altTextHealthWidgetDefinition = {\n slug: 'alt-text-health',\n // `Component` was renamed from `ComponentPath` in Payload 3.79.0. Set both for backward compatibility.\n Component: '@jhb.software/payload-alt-text-plugin/server#AltTextHealthWidget',\n ComponentPath: '@jhb.software/payload-alt-text-plugin/server#AltTextHealthWidget',\n label: {\n de: 'Alternativtexte Zustand',\n en: 'Alt text health',\n },\n maxWidth: 'full',\n minWidth: 'medium',\n} satisfies { ComponentPath: string } & Widget\n\ntype DashboardDefaultLayout = Config['admin'] extends infer TAdmin\n ? TAdmin extends { dashboard?: infer TDashboard }\n ? TDashboard extends { defaultLayout?: infer TDefaultLayout }\n ? TDefaultLayout\n : never\n : never\n : never\n\nconst defaultAltTextHealthWidgetLayout: WidgetInstance = {\n widgetSlug: 'alt-text-health',\n width: 'full',\n}\n\nfunction appendAltTextHealthWidgetToLayout(layout: WidgetInstance[]): WidgetInstance[] {\n if (layout.some((widget) => widget.widgetSlug === 'alt-text-health')) {\n return layout\n }\n\n return [...layout, defaultAltTextHealthWidgetLayout]\n}\n\nfunction getDashboardDefaultLayout(defaultLayout: DashboardDefaultLayout | undefined) {\n if (!defaultLayout) {\n return [\n {\n widgetSlug: 'collections',\n width: 'full',\n },\n defaultAltTextHealthWidgetLayout,\n ] satisfies WidgetInstance[]\n }\n\n if (Array.isArray(defaultLayout)) {\n return appendAltTextHealthWidgetToLayout(defaultLayout)\n }\n\n return async ({ req }: { req: PayloadRequest }) =>\n appendAltTextHealthWidgetToLayout(await defaultLayout({ req }))\n}\n\nexport const payloadAltTextPlugin =\n (incomingPluginConfig: IncomingAltTextPluginConfig) =>\n (incomingConfig: Config): Config => {\n const config = { ...incomingConfig }\n\n // If the plugin is disabled, return the config without modifying it\n if (incomingPluginConfig.enabled === false) {\n return config\n }\n\n const locales = config.localization\n ? config.localization.locales.map((localeConfig) =>\n typeof localeConfig === 'string' ? localeConfig : localeConfig.code,\n )\n : []\n\n const enableHealthCheck = incomingPluginConfig.healthCheck !== false\n\n const pluginConfig: AltTextPluginConfig = {\n access: incomingPluginConfig.access ?? (({ req }) => !!req.user),\n collections: incomingPluginConfig.collections,\n enabled: incomingPluginConfig.enabled ?? true,\n fieldsOverride: incomingPluginConfig.fieldsOverride,\n getImageThumbnail: incomingPluginConfig.getImageThumbnail,\n healthCheck: enableHealthCheck,\n locale: incomingPluginConfig.locale,\n locales,\n maxBulkGenerateConcurrency: incomingPluginConfig.maxBulkGenerateConcurrency ?? 16,\n resolver: incomingPluginConfig.resolver,\n }\n\n // Validate locale requirement for non-localized mode\n if (locales.length === 0 && !incomingPluginConfig.locale) {\n throw new Error(\n 'The alt-text plugin requires a \"locale\" option when Payload localization is disabled. ' +\n 'Please add { locale: \"en\" } (or your preferred locale) to your plugin configuration.',\n )\n }\n\n const defaultFields = [\n altTextField({\n localized: Boolean(config.localization),\n supportedMimeTypes: pluginConfig.resolver.supportedMimeTypes,\n }),\n keywordsField({\n localized: Boolean(config.localization),\n }),\n ]\n\n const fields =\n incomingPluginConfig.fieldsOverride &&\n typeof incomingPluginConfig.fieldsOverride === 'function'\n ? incomingPluginConfig.fieldsOverride({ defaultFields })\n : defaultFields\n\n // Ensure collections array exists\n config.collections = config.collections || []\n\n // Map over collections and inject AI alt text fields into specified ones\n config.collections = config.collections.map((collectionConfig) => {\n if (pluginConfig.collections.includes(collectionConfig.slug)) {\n if (!collectionConfig.upload) {\n console.warn(\n `AI Alt Text Plugin: Collection \"${collectionConfig.slug}\" is not an upload collection. Skipping field injection.`,\n )\n return collectionConfig\n }\n\n return {\n ...collectionConfig,\n admin: {\n ...collectionConfig.admin,\n components: {\n ...(collectionConfig.admin?.components ?? {}),\n // TODO: use the beforeBulkAction custom component slot once available: https://github.com/payloadcms/payload/pull/11719\n beforeListTable: [\n ...(collectionConfig.admin?.components?.beforeListTable ?? []),\n {\n path: '@jhb.software/payload-alt-text-plugin/client#BulkGenerateAltTextsButton',\n props: {\n collectionSlug: collectionConfig.slug,\n },\n },\n ],\n },\n // enhance the search by adding the filename, keywords and alt fields (if the user has not provided their own listSearchableFields)\n listSearchableFields: collectionConfig.admin?.listSearchableFields ?? [\n 'filename',\n 'keywords',\n 'alt',\n ],\n },\n fields: [...(collectionConfig.fields ?? []), ...fields],\n hooks: {\n ...collectionConfig.hooks,\n ...(enableHealthCheck && {\n afterChange: [\n ...(collectionConfig.hooks?.afterChange ?? []),\n createRevalidateAltTextHealthAfterChangeHook(collectionConfig.slug),\n ],\n afterDelete: [\n ...(collectionConfig.hooks?.afterDelete ?? []),\n createRevalidateAltTextHealthAfterDeleteHook(collectionConfig.slug),\n ],\n }),\n },\n }\n }\n\n return collectionConfig\n })\n\n const existingWidgets = config.admin?.dashboard?.widgets ?? []\n const widgets =\n !enableHealthCheck || existingWidgets.some((widget) => widget.slug === 'alt-text-health')\n ? existingWidgets\n : [...existingWidgets, altTextHealthWidgetDefinition]\n\n return {\n ...config,\n admin: {\n ...config.admin,\n dashboard: {\n ...config.admin?.dashboard,\n ...(enableHealthCheck && {\n defaultLayout: getDashboardDefaultLayout(config.admin?.dashboard?.defaultLayout),\n }),\n widgets,\n },\n },\n custom: {\n ...config.custom,\n // Make plugin config available in hooks/actions\n altTextPluginConfig: pluginConfig,\n },\n endpoints: [\n ...(config.endpoints ?? []),\n {\n handler: generateAltTextEndpoint(pluginConfig.access),\n method: 'post',\n path: '/alt-text-plugin/generate',\n },\n {\n handler: bulkGenerateAltTextsEndpoint(pluginConfig.access),\n method: 'post',\n path: '/alt-text-plugin/generate/bulk',\n },\n ...(enableHealthCheck\n ? [\n {\n handler: altTextHealthEndpoint(pluginConfig.access),\n method: 'get' as const,\n path: '/alt-text-plugin/health',\n },\n ]\n : []),\n ],\n i18n: {\n ...config.i18n,\n translations: deepMergeSimple(translations, incomingConfig.i18n?.translations ?? {}),\n },\n }\n }\n"],"names":["altTextHealthEndpoint","bulkGenerateAltTextsEndpoint","generateAltTextEndpoint","altTextField","keywordsField","createRevalidateAltTextHealthAfterChangeHook","createRevalidateAltTextHealthAfterDeleteHook","translations","deepMergeSimple","altTextHealthWidgetDefinition","slug","Component","ComponentPath","label","de","en","maxWidth","minWidth","defaultAltTextHealthWidgetLayout","widgetSlug","width","appendAltTextHealthWidgetToLayout","layout","some","widget","getDashboardDefaultLayout","defaultLayout","Array","isArray","req","payloadAltTextPlugin","incomingPluginConfig","incomingConfig","config","enabled","locales","localization","map","localeConfig","code","enableHealthCheck","healthCheck","pluginConfig","access","user","collections","fieldsOverride","getImageThumbnail","locale","maxBulkGenerateConcurrency","resolver","length","Error","defaultFields","localized","Boolean","supportedMimeTypes","fields","collectionConfig","includes","upload","console","warn","admin","components","beforeListTable","path","props","collectionSlug","listSearchableFields","hooks","afterChange","afterDelete","existingWidgets","dashboard","widgets","custom","altTextPluginConfig","endpoints","handler","method","i18n"],"mappings":"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"}
|
package/dist/resolvers/openAI.js
CHANGED
|
@@ -170,7 +170,14 @@ import { z } from 'zod';
|
|
|
170
170
|
success: false
|
|
171
171
|
};
|
|
172
172
|
}
|
|
173
|
-
}
|
|
173
|
+
},
|
|
174
|
+
// https://platform.openai.com/docs/guides/images-vision
|
|
175
|
+
supportedMimeTypes: [
|
|
176
|
+
'image/jpeg',
|
|
177
|
+
'image/png',
|
|
178
|
+
'image/gif',
|
|
179
|
+
'image/webp'
|
|
180
|
+
]
|
|
174
181
|
};
|
|
175
182
|
};
|
|
176
183
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/resolvers/openAI.ts"],"sourcesContent":["import type { AutoParseableResponseFormat } from 'openai/lib/parser.mjs'\nimport type { ChatCompletionContentPartText } from 'openai/resources/chat/completions.mjs'\nimport type { ResponseFormatJSONSchema } from 'openai/resources/shared.mjs'\n\nimport OpenAI from 'openai'\nimport { makeParseableResponseFormat } from 'openai/lib/parser.mjs'\nimport { z } from 'zod'\n\nimport type {\n AltTextBulkResolverArgs,\n AltTextBulkResolverResponse,\n AltTextResolver,\n AltTextResolverArgs,\n AltTextResolverResponse,\n} from './types.js'\n\nexport type OpenAIResolverConfig = {\n /** OpenAI API key for authentication */\n apiKey: string\n /**\n * The OpenAI LLM model to use for alt text generation.\n * @default 'gpt-4.1-nano'\n */\n model?: string\n}\n\n/**\n * Creates a chat completion `JSONSchema` response format object from\n * the given Zod schema.\n *\n * This is a temporary drop in replacement for the zodResponseFormat from openai/helpers/zod.ts\n * because of issue https://github.com/openai/openai-node/issues/1576\n */\nfunction zodResponseFormat<ZodInput extends z.ZodType>(\n zodObject: ZodInput,\n name: string,\n props?: Omit<ResponseFormatJSONSchema.JSONSchema, 'name' | 'schema' | 'strict'>,\n): AutoParseableResponseFormat<z.infer<ZodInput>> {\n return makeParseableResponseFormat(\n {\n type: 'json_schema',\n json_schema: {\n ...props,\n name,\n schema: z.toJSONSchema(zodObject, { target: 'draft-7' }),\n strict: true,\n },\n },\n (content) => zodObject.parse(JSON.parse(content)),\n )\n}\n\n/**\n * Creates an OpenAI-based resolver for alt text generation.\n *\n * @example\n * ```typescript\n * import { openAIResolver } from '@jhb.software/payload-alt-text-plugin'\n *\n * openAIResolver({\n * apiKey: process.env.OPENAI_API_KEY,\n * model: 'gpt-4.1-mini', // optional, defaults to 'gpt-4.1-nano'\n * })\n * ```\n */\nexport const openAIResolver = (config: OpenAIResolverConfig): AltTextResolver => {\n const { apiKey, model = 'gpt-4.1-nano' } = config\n\n return {\n key: 'openai',\n resolve: async ({\n filename,\n imageThumbnailUrl,\n locale,\n }: AltTextResolverArgs): Promise<AltTextResolverResponse> => {\n try {\n const openai = new OpenAI({ apiKey })\n\n const modelResponseSchema = z.object({\n altText: z.string().describe('A concise, descriptive alt text for the image'),\n keywords: z.array(z.string()).describe('Keywords that describe the content of the image'),\n })\n\n const response = await openai.chat.completions.parse({\n max_completion_tokens: 150,\n messages: [\n {\n content: `\n You are an expert at analyzing images and creating descriptive image alt text.\n\n Please analyze the given image and provide the following:\n - A concise, descriptive alt text (1-2 sentences) as \"altText\". Focus on the subject, action, and setting. Avoid phrases like 'Image of', 'A picture of', or 'Photo showing'. Be specific and include relevant details like location or context if visible. Make no assumptions.\n - A list of keywords that describe the content (e.g., [\"Camel\", \"Palm trees\", \"Desert\"]) as \"keywords\"\n\n If a context is provided, use it to enhance the alt text.\n\n Format your response as a JSON object. You must respond in the ${locale} language.\n `,\n role: 'system',\n },\n {\n content: [\n {\n type: 'image_url',\n image_url: { url: imageThumbnailUrl },\n },\n ...(filename\n ? [\n {\n type: 'text',\n text: filename,\n } satisfies ChatCompletionContentPartText,\n ]\n : []),\n ],\n role: 'user',\n },\n ],\n model,\n response_format: zodResponseFormat(modelResponseSchema, 'data'),\n })\n\n const result = response.choices[0]?.message?.parsed\n\n if (!result) {\n return { error: 'No result from OpenAI', success: false }\n }\n\n return {\n result,\n success: true,\n }\n } catch (error) {\n console.error('Error generating alt text:', error)\n return {\n error: error instanceof Error ? error.message : 'Unknown error',\n success: false,\n }\n }\n },\n resolveBulk: async ({\n filename,\n imageThumbnailUrl,\n locales,\n }: AltTextBulkResolverArgs): Promise<AltTextBulkResolverResponse> => {\n try {\n const openai = new OpenAI({ apiKey })\n\n const modelResponseSchema = z.object(\n Object.fromEntries(\n locales.map((locale) => [\n locale,\n z.object({\n altText: z.string().describe('A concise, descriptive alt text for the image'),\n keywords: z\n .array(z.string())\n .describe('Keywords that describe the content of the image'),\n }),\n ]),\n ),\n )\n\n const response = await openai.chat.completions.parse({\n max_completion_tokens: 300,\n messages: [\n {\n content: `\n You are an expert at analyzing images and creating descriptive image alt text.\n\n Please analyze the given image and provide the following in ${locales.join(', ')}:\n - A concise, localized descriptive alt text (1-2 sentences) as \"altText\". Focus on the subject, action, and setting. Avoid phrases like 'Image of', 'A picture of', or 'Photo showing'. Be specific and include relevant details like location or context if visible. Make no assumptions.\n - A localized list of keywords that describe the content (e.g., [\"Camel\", \"Palm trees\", \"Desert\"]) as \"keywords\"\n\n If a context is provided, use it to enhance the alt text.\n\n Format your response as a JSON object with ${locales.join(', ')} keys, each containing \"altText\" and \"keywords\".\n `,\n role: 'system',\n },\n {\n content: [\n {\n type: 'image_url',\n image_url: { url: imageThumbnailUrl },\n },\n ...(filename\n ? [\n {\n type: 'text',\n text: filename,\n } satisfies ChatCompletionContentPartText,\n ]\n : []),\n ],\n role: 'user',\n },\n ],\n model,\n response_format: zodResponseFormat(modelResponseSchema, 'data'),\n })\n\n const result = response.choices[0]?.message?.parsed\n\n if (!result) {\n return { error: 'No result from OpenAI', success: false }\n }\n\n return {\n results: result,\n success: true,\n }\n } catch (error) {\n console.error('Error generating bulk alt text:', error)\n return {\n error: error instanceof Error ? error.message : 'Unknown error',\n success: false,\n }\n }\n },\n }\n}\n"],"names":["OpenAI","makeParseableResponseFormat","z","zodResponseFormat","zodObject","name","props","type","json_schema","schema","toJSONSchema","target","strict","content","parse","JSON","openAIResolver","config","apiKey","model","key","resolve","filename","imageThumbnailUrl","locale","openai","modelResponseSchema","object","altText","string","describe","keywords","array","response","chat","completions","max_completion_tokens","messages","role","image_url","url","text","response_format","result","choices","message","parsed","error","success","console","Error","resolveBulk","locales","Object","fromEntries","map","join","results"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../src/resolvers/openAI.ts"],"sourcesContent":["import type { AutoParseableResponseFormat } from 'openai/lib/parser.mjs'\nimport type { ChatCompletionContentPartText } from 'openai/resources/chat/completions.mjs'\nimport type { ResponseFormatJSONSchema } from 'openai/resources/shared.mjs'\n\nimport OpenAI from 'openai'\nimport { makeParseableResponseFormat } from 'openai/lib/parser.mjs'\nimport { z } from 'zod'\n\nimport type {\n AltTextBulkResolverArgs,\n AltTextBulkResolverResponse,\n AltTextResolver,\n AltTextResolverArgs,\n AltTextResolverResponse,\n} from './types.js'\n\nexport type OpenAIResolverConfig = {\n /** OpenAI API key for authentication */\n apiKey: string\n /**\n * The OpenAI LLM model to use for alt text generation.\n * @default 'gpt-4.1-nano'\n */\n model?: string\n}\n\n/**\n * Creates a chat completion `JSONSchema` response format object from\n * the given Zod schema.\n *\n * This is a temporary drop in replacement for the zodResponseFormat from openai/helpers/zod.ts\n * because of issue https://github.com/openai/openai-node/issues/1576\n */\nfunction zodResponseFormat<ZodInput extends z.ZodType>(\n zodObject: ZodInput,\n name: string,\n props?: Omit<ResponseFormatJSONSchema.JSONSchema, 'name' | 'schema' | 'strict'>,\n): AutoParseableResponseFormat<z.infer<ZodInput>> {\n return makeParseableResponseFormat(\n {\n type: 'json_schema',\n json_schema: {\n ...props,\n name,\n schema: z.toJSONSchema(zodObject, { target: 'draft-7' }),\n strict: true,\n },\n },\n (content) => zodObject.parse(JSON.parse(content)),\n )\n}\n\n/**\n * Creates an OpenAI-based resolver for alt text generation.\n *\n * @example\n * ```typescript\n * import { openAIResolver } from '@jhb.software/payload-alt-text-plugin'\n *\n * openAIResolver({\n * apiKey: process.env.OPENAI_API_KEY,\n * model: 'gpt-4.1-mini', // optional, defaults to 'gpt-4.1-nano'\n * })\n * ```\n */\nexport const openAIResolver = (config: OpenAIResolverConfig): AltTextResolver => {\n const { apiKey, model = 'gpt-4.1-nano' } = config\n\n return {\n key: 'openai',\n resolve: async ({\n filename,\n imageThumbnailUrl,\n locale,\n }: AltTextResolverArgs): Promise<AltTextResolverResponse> => {\n try {\n const openai = new OpenAI({ apiKey })\n\n const modelResponseSchema = z.object({\n altText: z.string().describe('A concise, descriptive alt text for the image'),\n keywords: z.array(z.string()).describe('Keywords that describe the content of the image'),\n })\n\n const response = await openai.chat.completions.parse({\n max_completion_tokens: 150,\n messages: [\n {\n content: `\n You are an expert at analyzing images and creating descriptive image alt text.\n\n Please analyze the given image and provide the following:\n - A concise, descriptive alt text (1-2 sentences) as \"altText\". Focus on the subject, action, and setting. Avoid phrases like 'Image of', 'A picture of', or 'Photo showing'. Be specific and include relevant details like location or context if visible. Make no assumptions.\n - A list of keywords that describe the content (e.g., [\"Camel\", \"Palm trees\", \"Desert\"]) as \"keywords\"\n\n If a context is provided, use it to enhance the alt text.\n\n Format your response as a JSON object. You must respond in the ${locale} language.\n `,\n role: 'system',\n },\n {\n content: [\n {\n type: 'image_url',\n image_url: { url: imageThumbnailUrl },\n },\n ...(filename\n ? [\n {\n type: 'text',\n text: filename,\n } satisfies ChatCompletionContentPartText,\n ]\n : []),\n ],\n role: 'user',\n },\n ],\n model,\n response_format: zodResponseFormat(modelResponseSchema, 'data'),\n })\n\n const result = response.choices[0]?.message?.parsed\n\n if (!result) {\n return { error: 'No result from OpenAI', success: false }\n }\n\n return {\n result,\n success: true,\n }\n } catch (error) {\n console.error('Error generating alt text:', error)\n return {\n error: error instanceof Error ? error.message : 'Unknown error',\n success: false,\n }\n }\n },\n resolveBulk: async ({\n filename,\n imageThumbnailUrl,\n locales,\n }: AltTextBulkResolverArgs): Promise<AltTextBulkResolverResponse> => {\n try {\n const openai = new OpenAI({ apiKey })\n\n const modelResponseSchema = z.object(\n Object.fromEntries(\n locales.map((locale) => [\n locale,\n z.object({\n altText: z.string().describe('A concise, descriptive alt text for the image'),\n keywords: z\n .array(z.string())\n .describe('Keywords that describe the content of the image'),\n }),\n ]),\n ),\n )\n\n const response = await openai.chat.completions.parse({\n max_completion_tokens: 300,\n messages: [\n {\n content: `\n You are an expert at analyzing images and creating descriptive image alt text.\n\n Please analyze the given image and provide the following in ${locales.join(', ')}:\n - A concise, localized descriptive alt text (1-2 sentences) as \"altText\". Focus on the subject, action, and setting. Avoid phrases like 'Image of', 'A picture of', or 'Photo showing'. Be specific and include relevant details like location or context if visible. Make no assumptions.\n - A localized list of keywords that describe the content (e.g., [\"Camel\", \"Palm trees\", \"Desert\"]) as \"keywords\"\n\n If a context is provided, use it to enhance the alt text.\n\n Format your response as a JSON object with ${locales.join(', ')} keys, each containing \"altText\" and \"keywords\".\n `,\n role: 'system',\n },\n {\n content: [\n {\n type: 'image_url',\n image_url: { url: imageThumbnailUrl },\n },\n ...(filename\n ? [\n {\n type: 'text',\n text: filename,\n } satisfies ChatCompletionContentPartText,\n ]\n : []),\n ],\n role: 'user',\n },\n ],\n model,\n response_format: zodResponseFormat(modelResponseSchema, 'data'),\n })\n\n const result = response.choices[0]?.message?.parsed\n\n if (!result) {\n return { error: 'No result from OpenAI', success: false }\n }\n\n return {\n results: result,\n success: true,\n }\n } catch (error) {\n console.error('Error generating bulk alt text:', error)\n return {\n error: error instanceof Error ? error.message : 'Unknown error',\n success: false,\n }\n }\n },\n // https://platform.openai.com/docs/guides/images-vision\n supportedMimeTypes: ['image/jpeg', 'image/png', 'image/gif', 'image/webp'],\n }\n}\n"],"names":["OpenAI","makeParseableResponseFormat","z","zodResponseFormat","zodObject","name","props","type","json_schema","schema","toJSONSchema","target","strict","content","parse","JSON","openAIResolver","config","apiKey","model","key","resolve","filename","imageThumbnailUrl","locale","openai","modelResponseSchema","object","altText","string","describe","keywords","array","response","chat","completions","max_completion_tokens","messages","role","image_url","url","text","response_format","result","choices","message","parsed","error","success","console","Error","resolveBulk","locales","Object","fromEntries","map","join","results","supportedMimeTypes"],"mappings":"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"}
|
|
@@ -65,4 +65,6 @@ export type AltTextResolver = {
|
|
|
65
65
|
resolve: (args: AltTextResolverArgs) => Promise<AltTextResolverResponse>;
|
|
66
66
|
/** Generate alt text for a single image in multiple locales (bulk operation) */
|
|
67
67
|
resolveBulk: (args: AltTextBulkResolverArgs) => Promise<AltTextBulkResolverResponse>;
|
|
68
|
+
/** MIME types this resolver can process. When set, the endpoint rejects files whose mimeType is not in this list. */
|
|
69
|
+
supportedMimeTypes?: string[];
|
|
68
70
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/resolvers/types.ts"],"sourcesContent":["import type { PayloadRequest } from 'payload'\n\n/**\n * Result of generating alt text for a single image.\n */\nexport type AltTextResult = {\n /** Concise descriptive alt text (1-2 sentences) */\n altText: string\n /** Keywords describing the image content */\n keywords: string[]\n}\n\n/**\n * Arguments passed to the resolver for single image generation.\n */\nexport type AltTextResolverArgs = {\n /** Optional filename for additional context */\n filename?: string\n /** URL of the image thumbnail (must be publicly accessible) */\n imageThumbnailUrl: string\n /** Target locale for the generated alt text */\n locale: string\n /** Payload request object for logging */\n req: PayloadRequest\n}\n\n/**\n * Arguments passed to the resolver for bulk/multi-locale generation.\n */\nexport type AltTextBulkResolverArgs = {\n /** Optional filename for additional context */\n filename?: string\n /** URL of the image thumbnail (must be publicly accessible) */\n imageThumbnailUrl: string\n /** Target locales for the generated alt texts */\n locales: string[]\n /** Payload request object for logging */\n req: PayloadRequest\n}\n\n/**\n * Response from single image alt text generation.\n */\nexport type AltTextResolverResponse =\n | { error?: string; success: false }\n | { result: AltTextResult; success: true }\n\n/**\n * Response from bulk/multi-locale alt text generation.\n */\nexport type AltTextBulkResolverResponse =\n | { error?: string; success: false }\n | { results: Record<string, AltTextResult>; success: true }\n\n/**\n * Alt text resolver interface.\n * Implement this to create custom resolvers for different providers.\n */\nexport type AltTextResolver = {\n /** Unique key identifying this resolver */\n key: string\n /** Generate alt text for a single image in one locale */\n resolve: (args: AltTextResolverArgs) => Promise<AltTextResolverResponse>\n /** Generate alt text for a single image in multiple locales (bulk operation) */\n resolveBulk: (args: AltTextBulkResolverArgs) => Promise<AltTextBulkResolverResponse>\n}\n"],"names":[],"mappings":"AAsDA;;;CAGC,GACD,
|
|
1
|
+
{"version":3,"sources":["../../src/resolvers/types.ts"],"sourcesContent":["import type { PayloadRequest } from 'payload'\n\n/**\n * Result of generating alt text for a single image.\n */\nexport type AltTextResult = {\n /** Concise descriptive alt text (1-2 sentences) */\n altText: string\n /** Keywords describing the image content */\n keywords: string[]\n}\n\n/**\n * Arguments passed to the resolver for single image generation.\n */\nexport type AltTextResolverArgs = {\n /** Optional filename for additional context */\n filename?: string\n /** URL of the image thumbnail (must be publicly accessible) */\n imageThumbnailUrl: string\n /** Target locale for the generated alt text */\n locale: string\n /** Payload request object for logging */\n req: PayloadRequest\n}\n\n/**\n * Arguments passed to the resolver for bulk/multi-locale generation.\n */\nexport type AltTextBulkResolverArgs = {\n /** Optional filename for additional context */\n filename?: string\n /** URL of the image thumbnail (must be publicly accessible) */\n imageThumbnailUrl: string\n /** Target locales for the generated alt texts */\n locales: string[]\n /** Payload request object for logging */\n req: PayloadRequest\n}\n\n/**\n * Response from single image alt text generation.\n */\nexport type AltTextResolverResponse =\n | { error?: string; success: false }\n | { result: AltTextResult; success: true }\n\n/**\n * Response from bulk/multi-locale alt text generation.\n */\nexport type AltTextBulkResolverResponse =\n | { error?: string; success: false }\n | { results: Record<string, AltTextResult>; success: true }\n\n/**\n * Alt text resolver interface.\n * Implement this to create custom resolvers for different providers.\n */\nexport type AltTextResolver = {\n /** Unique key identifying this resolver */\n key: string\n /** Generate alt text for a single image in one locale */\n resolve: (args: AltTextResolverArgs) => Promise<AltTextResolverResponse>\n /** Generate alt text for a single image in multiple locales (bulk operation) */\n resolveBulk: (args: AltTextBulkResolverArgs) => Promise<AltTextBulkResolverResponse>\n /** MIME types this resolver can process. When set, the endpoint rejects files whose mimeType is not in this list. */\n supportedMimeTypes?: string[]\n}\n"],"names":[],"mappings":"AAsDA;;;CAGC,GACD,WASC"}
|
package/dist/translations/de.js
CHANGED
|
@@ -15,9 +15,9 @@ export const de = {
|
|
|
15
15
|
cannotGenerateMissingFields: 'Alternativtext kann nicht generiert werden. Erforderliche Felder fehlen.',
|
|
16
16
|
errorGeneratingAltText: 'Fehler beim Generieren des Alternativtextes. Bitte versuchen Sie es erneut.',
|
|
17
17
|
failedToGenerate: 'Generierung des Alternativtextes fehlgeschlagen. Bitte versuchen Sie es erneut.',
|
|
18
|
-
failedToGenerateForXImages: 'Generierung des Alternativtextes für {
|
|
18
|
+
failedToGenerateForXImages: 'Generierung des Alternativtextes für {{count}} Bilder fehlgeschlagen.',
|
|
19
19
|
noAltTextGenerated: 'Kein Alternativtext generiert. Bitte versuchen Sie es erneut.',
|
|
20
|
-
xOfYImagesUpdated: '{
|
|
20
|
+
xOfYImagesUpdated: '{{updated}} von {{total}} Bildern aktualisiert.',
|
|
21
21
|
// Help text
|
|
22
22
|
altTextDescription: 'Alternativtext für das Bild. Dieser wird für Screenreader und SEO verwendet. Er sollte die folgenden Anforderungen erfüllen:',
|
|
23
23
|
altTextRequirement1: 'Beschreibt in 1-2 Sätzen, was auf dem Bild zu sehen ist.',
|
|
@@ -25,18 +25,19 @@ export const de = {
|
|
|
25
25
|
altTextRequirement3: 'Phrasen wie "Bild von" oder "Foto von" sind überflüssig, da Screenreader bereits anzeigen, dass es sich um ein Bild handelt.',
|
|
26
26
|
// Tooltips
|
|
27
27
|
pleaseSaveDocumentFirst: 'Bitte speichern Sie zuerst das Dokument',
|
|
28
|
+
unsupportedMimeType: 'Alternativtext-Generierung wird für {{mimeType}}-Dateien nicht unterstützt',
|
|
28
29
|
// Validation messages
|
|
29
30
|
theAlternateTextIsRequired: 'Der Alternativtext ist erforderlich.',
|
|
30
31
|
// Dashboard widget
|
|
31
|
-
altTextHealthDescription: '
|
|
32
|
-
altTextHealthWidget: '
|
|
33
|
-
collectionCheckFailed: '
|
|
32
|
+
altTextHealthDescription: 'Alternativtext-Abdeckung über alle Upload-Sammlungen.',
|
|
33
|
+
altTextHealthWidget: 'Alternativtext',
|
|
34
|
+
collectionCheckFailed: 'Status nicht verfügbar',
|
|
34
35
|
healthCheckPartialWarning: 'Einige Sammlungen konnten gerade nicht geprüft werden.',
|
|
35
|
-
localeCount: '{count} Sprachen',
|
|
36
|
+
localeCount: '{{count}} Sprachen',
|
|
36
37
|
noImagesFound: 'In den konfigurierten Sammlungen wurden noch keine Bilder gefunden.',
|
|
37
|
-
statusHealthy: '
|
|
38
|
-
statusUnhealthy: '
|
|
39
|
-
totalImageCount: '{count} Bilder'
|
|
38
|
+
statusHealthy: 'Alle vorhanden',
|
|
39
|
+
statusUnhealthy: '{{count}} fehlend',
|
|
40
|
+
totalImageCount: '{{count}} Bilder'
|
|
40
41
|
}
|
|
41
42
|
};
|
|
42
43
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/translations/de.ts"],"sourcesContent":["import type { GenericTranslationsObject } from './index.js'\n\nexport const de: GenericTranslationsObject = {\n $schema: './translation-schema.json',\n '@jhb.software/payload-alt-text-plugin': {\n // Field labels\n alternateText: 'Alternativtext',\n keywords: 'Schlüsselwörter',\n keywordsDescription:\n 'Schlüsselwörter, die das Bild beschreiben. Wird bei der Suche nach dem Bild verwendet.',\n\n // Button labels\n generateAltText: 'Alternativtext generieren',\n generateAltTextFor: 'Alternativtext generieren für',\n image: 'Bild',\n images: 'Bilder',\n\n // Toast messages\n altTextGeneratedSuccess:\n 'Alternativtext erfolgreich generiert. Bitte überprüfen und speichern Sie das Dokument.',\n cannotGenerateMissingFields:\n 'Alternativtext kann nicht generiert werden. Erforderliche Felder fehlen.',\n errorGeneratingAltText:\n 'Fehler beim Generieren des Alternativtextes. Bitte versuchen Sie es erneut.',\n failedToGenerate:\n 'Generierung des Alternativtextes fehlgeschlagen. Bitte versuchen Sie es erneut.',\n failedToGenerateForXImages
|
|
1
|
+
{"version":3,"sources":["../../src/translations/de.ts"],"sourcesContent":["import type { GenericTranslationsObject } from './index.js'\n\nexport const de: GenericTranslationsObject = {\n $schema: './translation-schema.json',\n '@jhb.software/payload-alt-text-plugin': {\n // Field labels\n alternateText: 'Alternativtext',\n keywords: 'Schlüsselwörter',\n keywordsDescription:\n 'Schlüsselwörter, die das Bild beschreiben. Wird bei der Suche nach dem Bild verwendet.',\n\n // Button labels\n generateAltText: 'Alternativtext generieren',\n generateAltTextFor: 'Alternativtext generieren für',\n image: 'Bild',\n images: 'Bilder',\n\n // Toast messages\n altTextGeneratedSuccess:\n 'Alternativtext erfolgreich generiert. Bitte überprüfen und speichern Sie das Dokument.',\n cannotGenerateMissingFields:\n 'Alternativtext kann nicht generiert werden. Erforderliche Felder fehlen.',\n errorGeneratingAltText:\n 'Fehler beim Generieren des Alternativtextes. Bitte versuchen Sie es erneut.',\n failedToGenerate:\n 'Generierung des Alternativtextes fehlgeschlagen. Bitte versuchen Sie es erneut.',\n failedToGenerateForXImages:\n 'Generierung des Alternativtextes für {{count}} Bilder fehlgeschlagen.',\n noAltTextGenerated: 'Kein Alternativtext generiert. Bitte versuchen Sie es erneut.',\n xOfYImagesUpdated: '{{updated}} von {{total}} Bildern aktualisiert.',\n\n // Help text\n altTextDescription:\n 'Alternativtext für das Bild. Dieser wird für Screenreader und SEO verwendet. Er sollte die folgenden Anforderungen erfüllen:',\n altTextRequirement1: 'Beschreibt in 1-2 Sätzen, was auf dem Bild zu sehen ist.',\n altTextRequirement2:\n 'Sollte möglichst die gleichen Informationen oder den gleichen Zweck wie das Bild vermitteln.',\n altTextRequirement3:\n 'Phrasen wie \"Bild von\" oder \"Foto von\" sind überflüssig, da Screenreader bereits anzeigen, dass es sich um ein Bild handelt.',\n\n // Tooltips\n pleaseSaveDocumentFirst: 'Bitte speichern Sie zuerst das Dokument',\n unsupportedMimeType:\n 'Alternativtext-Generierung wird für {{mimeType}}-Dateien nicht unterstützt',\n\n // Validation messages\n theAlternateTextIsRequired: 'Der Alternativtext ist erforderlich.',\n\n // Dashboard widget\n altTextHealthDescription: 'Alternativtext-Abdeckung über alle Upload-Sammlungen.',\n altTextHealthWidget: 'Alternativtext',\n collectionCheckFailed: 'Status nicht verfügbar',\n healthCheckPartialWarning: 'Einige Sammlungen konnten gerade nicht geprüft werden.',\n localeCount: '{{count}} Sprachen',\n noImagesFound: 'In den konfigurierten Sammlungen wurden noch keine Bilder gefunden.',\n statusHealthy: 'Alle vorhanden',\n statusUnhealthy: '{{count}} fehlend',\n totalImageCount: '{{count}} Bilder',\n },\n}\n"],"names":["de","$schema","alternateText","keywords","keywordsDescription","generateAltText","generateAltTextFor","image","images","altTextGeneratedSuccess","cannotGenerateMissingFields","errorGeneratingAltText","failedToGenerate","failedToGenerateForXImages","noAltTextGenerated","xOfYImagesUpdated","altTextDescription","altTextRequirement1","altTextRequirement2","altTextRequirement3","pleaseSaveDocumentFirst","unsupportedMimeType","theAlternateTextIsRequired","altTextHealthDescription","altTextHealthWidget","collectionCheckFailed","healthCheckPartialWarning","localeCount","noImagesFound","statusHealthy","statusUnhealthy","totalImageCount"],"mappings":"AAEA,OAAO,MAAMA,KAAgC;IAC3CC,SAAS;IACT,yCAAyC;QACvC,eAAe;QACfC,eAAe;QACfC,UAAU;QACVC,qBACE;QAEF,gBAAgB;QAChBC,iBAAiB;QACjBC,oBAAoB;QACpBC,OAAO;QACPC,QAAQ;QAER,iBAAiB;QACjBC,yBACE;QACFC,6BACE;QACFC,wBACE;QACFC,kBACE;QACFC,4BACE;QACFC,oBAAoB;QACpBC,mBAAmB;QAEnB,YAAY;QACZC,oBACE;QACFC,qBAAqB;QACrBC,qBACE;QACFC,qBACE;QAEF,WAAW;QACXC,yBAAyB;QACzBC,qBACE;QAEF,sBAAsB;QACtBC,4BAA4B;QAE5B,mBAAmB;QACnBC,0BAA0B;QAC1BC,qBAAqB;QACrBC,uBAAuB;QACvBC,2BAA2B;QAC3BC,aAAa;QACbC,eAAe;QACfC,eAAe;QACfC,iBAAiB;QACjBC,iBAAiB;IACnB;AACF,EAAC"}
|
package/dist/translations/en.js
CHANGED
|
@@ -15,9 +15,9 @@ export const en = {
|
|
|
15
15
|
cannotGenerateMissingFields: 'Cannot generate alt text. Missing required fields.',
|
|
16
16
|
errorGeneratingAltText: 'Error generating alt text. Please try again.',
|
|
17
17
|
failedToGenerate: 'Failed to generate alt text. Please try again.',
|
|
18
|
-
failedToGenerateForXImages: 'Failed to generate alt text for {
|
|
18
|
+
failedToGenerateForXImages: 'Failed to generate alt text for {{count}} images.',
|
|
19
19
|
noAltTextGenerated: 'No alt text generated. Please try again.',
|
|
20
|
-
xOfYImagesUpdated: '{
|
|
20
|
+
xOfYImagesUpdated: '{{updated}} of {{total}} images updated.',
|
|
21
21
|
// Help text
|
|
22
22
|
altTextDescription: 'Alternate text for the image. This will be used for screen readers and SEO. It should meet the following requirements:',
|
|
23
23
|
altTextRequirement1: 'Describes in 1-2 sentences, what is visible in the image.',
|
|
@@ -25,18 +25,19 @@ export const en = {
|
|
|
25
25
|
altTextRequirement3: 'Phrases like "image of" or "picture of" are unnecessary, since screen readers already announce that it\'s an image.',
|
|
26
26
|
// Tooltips
|
|
27
27
|
pleaseSaveDocumentFirst: 'Please save the document first',
|
|
28
|
+
unsupportedMimeType: 'Alt text generation is not supported for {{mimeType}} files',
|
|
28
29
|
// Validation messages
|
|
29
30
|
theAlternateTextIsRequired: 'An alternate text is required.',
|
|
30
31
|
// Dashboard widget
|
|
31
|
-
altTextHealthDescription: 'Alt text
|
|
32
|
-
altTextHealthWidget: 'Alt
|
|
33
|
-
collectionCheckFailed: '
|
|
32
|
+
altTextHealthDescription: 'Alt text coverage across upload collections.',
|
|
33
|
+
altTextHealthWidget: 'Alt Text',
|
|
34
|
+
collectionCheckFailed: 'Status unavailable',
|
|
34
35
|
healthCheckPartialWarning: 'Some collections could not be checked right now.',
|
|
35
|
-
localeCount: '{count} locales',
|
|
36
|
+
localeCount: '{{count}} locales',
|
|
36
37
|
noImagesFound: 'No images found in the configured collections yet.',
|
|
37
38
|
statusHealthy: 'All set',
|
|
38
|
-
statusUnhealthy: 'missing
|
|
39
|
-
totalImageCount: '{count} images'
|
|
39
|
+
statusUnhealthy: '{{count}} missing',
|
|
40
|
+
totalImageCount: '{{count}} images'
|
|
40
41
|
}
|
|
41
42
|
};
|
|
42
43
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/translations/en.ts"],"sourcesContent":["import type { GenericTranslationsObject } from './index.js'\n\nexport const en: GenericTranslationsObject = {\n $schema: './translation-schema.json',\n '@jhb.software/payload-alt-text-plugin': {\n // Field labels\n alternateText: 'Alternate text',\n keywords: 'Keywords',\n keywordsDescription: 'Keywords which describe the image. Used when searching for the image.',\n\n // Button labels\n generateAltText: 'Generate alt text',\n generateAltTextFor: 'Generate alt text for',\n image: 'image',\n images: 'images',\n\n // Toast messages\n altTextGeneratedSuccess:\n 'Alt text generated successfully. Please review and save the document.',\n cannotGenerateMissingFields: 'Cannot generate alt text. Missing required fields.',\n errorGeneratingAltText: 'Error generating alt text. Please try again.',\n failedToGenerate: 'Failed to generate alt text. Please try again.',\n failedToGenerateForXImages: 'Failed to generate alt text for {
|
|
1
|
+
{"version":3,"sources":["../../src/translations/en.ts"],"sourcesContent":["import type { GenericTranslationsObject } from './index.js'\n\nexport const en: GenericTranslationsObject = {\n $schema: './translation-schema.json',\n '@jhb.software/payload-alt-text-plugin': {\n // Field labels\n alternateText: 'Alternate text',\n keywords: 'Keywords',\n keywordsDescription: 'Keywords which describe the image. Used when searching for the image.',\n\n // Button labels\n generateAltText: 'Generate alt text',\n generateAltTextFor: 'Generate alt text for',\n image: 'image',\n images: 'images',\n\n // Toast messages\n altTextGeneratedSuccess:\n 'Alt text generated successfully. Please review and save the document.',\n cannotGenerateMissingFields: 'Cannot generate alt text. Missing required fields.',\n errorGeneratingAltText: 'Error generating alt text. Please try again.',\n failedToGenerate: 'Failed to generate alt text. Please try again.',\n failedToGenerateForXImages: 'Failed to generate alt text for {{count}} images.',\n noAltTextGenerated: 'No alt text generated. Please try again.',\n xOfYImagesUpdated: '{{updated}} of {{total}} images updated.',\n\n // Help text\n altTextDescription:\n 'Alternate text for the image. This will be used for screen readers and SEO. It should meet the following requirements:',\n altTextRequirement1: 'Describes in 1-2 sentences, what is visible in the image.',\n altTextRequirement2:\n 'Should convey the same information or purpose as the image, whenever possible.',\n altTextRequirement3:\n 'Phrases like \"image of\" or \"picture of\" are unnecessary, since screen readers already announce that it\\'s an image.',\n\n // Tooltips\n pleaseSaveDocumentFirst: 'Please save the document first',\n unsupportedMimeType: 'Alt text generation is not supported for {{mimeType}} files',\n\n // Validation messages\n theAlternateTextIsRequired: 'An alternate text is required.',\n\n // Dashboard widget\n altTextHealthDescription: 'Alt text coverage across upload collections.',\n altTextHealthWidget: 'Alt Text',\n collectionCheckFailed: 'Status unavailable',\n healthCheckPartialWarning: 'Some collections could not be checked right now.',\n localeCount: '{{count}} locales',\n noImagesFound: 'No images found in the configured collections yet.',\n statusHealthy: 'All set',\n statusUnhealthy: '{{count}} missing',\n totalImageCount: '{{count}} images',\n },\n}\n"],"names":["en","$schema","alternateText","keywords","keywordsDescription","generateAltText","generateAltTextFor","image","images","altTextGeneratedSuccess","cannotGenerateMissingFields","errorGeneratingAltText","failedToGenerate","failedToGenerateForXImages","noAltTextGenerated","xOfYImagesUpdated","altTextDescription","altTextRequirement1","altTextRequirement2","altTextRequirement3","pleaseSaveDocumentFirst","unsupportedMimeType","theAlternateTextIsRequired","altTextHealthDescription","altTextHealthWidget","collectionCheckFailed","healthCheckPartialWarning","localeCount","noImagesFound","statusHealthy","statusUnhealthy","totalImageCount"],"mappings":"AAEA,OAAO,MAAMA,KAAgC;IAC3CC,SAAS;IACT,yCAAyC;QACvC,eAAe;QACfC,eAAe;QACfC,UAAU;QACVC,qBAAqB;QAErB,gBAAgB;QAChBC,iBAAiB;QACjBC,oBAAoB;QACpBC,OAAO;QACPC,QAAQ;QAER,iBAAiB;QACjBC,yBACE;QACFC,6BAA6B;QAC7BC,wBAAwB;QACxBC,kBAAkB;QAClBC,4BAA4B;QAC5BC,oBAAoB;QACpBC,mBAAmB;QAEnB,YAAY;QACZC,oBACE;QACFC,qBAAqB;QACrBC,qBACE;QACFC,qBACE;QAEF,WAAW;QACXC,yBAAyB;QACzBC,qBAAqB;QAErB,sBAAsB;QACtBC,4BAA4B;QAE5B,mBAAmB;QACnBC,0BAA0B;QAC1BC,qBAAqB;QACrBC,uBAAuB;QACvBC,2BAA2B;QAC3BC,aAAa;QACbC,eAAe;QACfC,eAAe;QACfC,iBAAiB;QACjBC,iBAAiB;IACnB;AACF,EAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jhb.software/payload-alt-text-plugin",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.3",
|
|
4
4
|
"description": "A Payload CMS plugin that adds AI-powered alt text generation for images.",
|
|
5
5
|
"bugs": "https://github.com/jhb-software/payload-plugins/issues",
|
|
6
6
|
"repository": "https://github.com/jhb-software/payload-plugins",
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
"zod": "^4.3.6"
|
|
23
23
|
},
|
|
24
24
|
"peerDependencies": {
|
|
25
|
+
"@payloadcms/translations": "^3.81.0",
|
|
25
26
|
"@payloadcms/ui": "^3.81.0",
|
|
26
27
|
"next": "15.4.11",
|
|
27
28
|
"payload": "^3.81.0",
|
|
@@ -30,6 +31,7 @@
|
|
|
30
31
|
},
|
|
31
32
|
"devDependencies": {
|
|
32
33
|
"@payloadcms/eslint-config": "^3.28.0",
|
|
34
|
+
"@payloadcms/translations": "^3.81.0",
|
|
33
35
|
"@swc/cli": "^0.8.1",
|
|
34
36
|
"@swc/core": "^1.15.24",
|
|
35
37
|
"@types/react": "19.2.14",
|