@jhb.software/payload-alt-text-plugin 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/README.md +86 -0
  2. package/dist/components/AltTextHealthWidget.d.ts +2 -0
  3. package/dist/components/AltTextHealthWidget.js +199 -0
  4. package/dist/components/AltTextHealthWidget.js.map +1 -0
  5. package/dist/components/BulkGenerateAltTextsButton.js +10 -11
  6. package/dist/components/BulkGenerateAltTextsButton.js.map +1 -1
  7. package/dist/components/GenerateAltTextButton.js +14 -15
  8. package/dist/components/GenerateAltTextButton.js.map +1 -1
  9. package/dist/endpoints/altTextHealth.d.ts +3 -0
  10. package/dist/endpoints/altTextHealth.js +30 -0
  11. package/dist/endpoints/altTextHealth.js.map +1 -0
  12. package/dist/endpoints/bulkGenerateAltTexts.d.ts +2 -1
  13. package/dist/endpoints/bulkGenerateAltTexts.js +89 -73
  14. package/dist/endpoints/bulkGenerateAltTexts.js.map +1 -1
  15. package/dist/endpoints/generateAltText.d.ts +8 -2
  16. package/dist/endpoints/generateAltText.js +112 -75
  17. package/dist/endpoints/generateAltText.js.map +1 -1
  18. package/dist/exports/server.d.ts +3 -0
  19. package/dist/exports/server.js +4 -0
  20. package/dist/exports/server.js.map +1 -0
  21. package/dist/fields/altTextField.js +14 -9
  22. package/dist/fields/altTextField.js.map +1 -1
  23. package/dist/hooks/revalidateAltTextHealth.d.ts +3 -0
  24. package/dist/hooks/revalidateAltTextHealth.js +32 -0
  25. package/dist/hooks/revalidateAltTextHealth.js.map +1 -0
  26. package/dist/plugin.js +86 -6
  27. package/dist/plugin.js.map +1 -1
  28. package/dist/translations/de.js +11 -1
  29. package/dist/translations/de.js.map +1 -1
  30. package/dist/translations/en.js +11 -1
  31. package/dist/translations/en.js.map +1 -1
  32. package/dist/translations/translation-schema.json +44 -26
  33. package/dist/types/AltTextPluginConfig.d.ts +23 -1
  34. package/dist/types/AltTextPluginConfig.js.map +1 -1
  35. package/dist/utilities/altTextHealth.d.ts +37 -0
  36. package/dist/utilities/altTextHealth.js +150 -0
  37. package/dist/utilities/altTextHealth.js.map +1 -0
  38. package/dist/utilities/altTextHealthCache.d.ts +11 -0
  39. package/dist/utilities/altTextHealthCache.js +8 -0
  40. package/dist/utilities/altTextHealthCache.js.map +1 -0
  41. package/dist/utilities/altTextHealthWidgetDisplay.d.ts +6 -0
  42. package/dist/utilities/altTextHealthWidgetDisplay.js +11 -0
  43. package/dist/utilities/altTextHealthWidgetDisplay.js.map +1 -0
  44. package/dist/utilities/getCollectionLabel.d.ts +2 -0
  45. package/dist/utilities/getCollectionLabel.js +17 -0
  46. package/dist/utilities/getCollectionLabel.js.map +1 -0
  47. package/dist/utilities/summarizeCollection.d.ts +17 -0
  48. package/dist/utilities/summarizeCollection.js +62 -0
  49. package/dist/utilities/summarizeCollection.js.map +1 -0
  50. package/package.json +59 -38
  51. package/dist/utils/usePluginTranslation.d.ts +0 -5
  52. package/dist/utils/usePluginTranslation.js +0 -16
  53. package/dist/utils/usePluginTranslation.js.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/utilities/altTextHealth.ts"],"sourcesContent":["import type { Payload, PayloadRequest } from 'payload'\n\nimport { unstable_cache } from 'next/cache.js'\n\nimport type { AltTextPluginConfig } from '../types/AltTextPluginConfig.js'\n\nimport { createCachedAltTextHealthScan } from './altTextHealthCache.js'\nimport { localesFromConfig } from './localesFromConfig.js'\nimport { summarizeCollection } from './summarizeCollection.js'\n\nexport const ALT_TEXT_HEALTH_PLUGIN_SLUG = 'alt-text'\nexport const ALT_TEXT_HEALTH_CACHE_TTL = 3600\nexport const ALT_TEXT_HEALTH_GLOBAL_TAG = 'alt-text-health'\n\nexport type AltTextHealthErrorCode =\n | 'ALT_TEXT_COLLECTION_READ_FAILED'\n | 'ALT_TEXT_PLUGIN_CONFIG_MISSING'\n\nexport type AltTextHealthError = {\n code: AltTextHealthErrorCode\n collection?: string\n message: string\n operation?: 'find'\n}\n\nexport type AltTextHealthScanCollection = {\n collection: string\n completeDocs: number\n error?: AltTextHealthError\n invalidDocIds: (number | string)[] | undefined\n missingDocs: number\n partialDocs: number\n totalDocs: number\n}\n\nexport type AltTextHealthScan = {\n checkedAt: string\n collections: AltTextHealthScanCollection[]\n errors: AltTextHealthError[]\n isLocalized: boolean\n localeCodes: string[]\n}\n\nexport type AltTextHealthWidgetData = {\n collections: AltTextHealthScanCollection[]\n errors: AltTextHealthError[]\n isLocalized: boolean\n localeCount: number\n totalDocs: number\n}\n\ntype AltTextHealthComputationArgs = {\n collections: string[]\n isLocalized: boolean\n localeCodes: string[]\n payload: Payload\n}\n\nconst createUnknownScan = ({\n error,\n isLocalized,\n localeCodes,\n}: {\n error: AltTextHealthScan['errors'][number]\n isLocalized: boolean\n localeCodes: string[]\n}): AltTextHealthScan => ({\n checkedAt: new Date().toISOString(),\n collections: [],\n errors: [error],\n isLocalized,\n localeCodes,\n})\n\nconst createCollectionReadError = (collection: string, message: string) => ({\n code: 'ALT_TEXT_COLLECTION_READ_FAILED' as const,\n collection,\n message,\n operation: 'find' as const,\n})\n\nconst PAGE_SIZE = 500\n\nasync function fetchAllDocs(\n payload: Payload,\n collection: string,\n isLocalized: boolean,\n): Promise<{ alt: unknown; id: number | string }[]> {\n const docs: { alt: unknown; id: number | string }[] = []\n let page = 1\n let hasMore = true\n\n while (hasMore) {\n const result = await payload.find({\n collection,\n depth: 0,\n fallbackLocale: isLocalized ? false : undefined,\n limit: PAGE_SIZE,\n locale: isLocalized ? 'all' : undefined,\n overrideAccess: true,\n page,\n select: {\n alt: true,\n },\n })\n\n for (const doc of result.docs) {\n docs.push({\n id: doc.id,\n alt: 'alt' in doc ? doc.alt : undefined,\n })\n }\n\n hasMore = result.hasNextPage\n page++\n }\n\n return docs\n}\n\nasync function computeAltTextHealthScan({\n collections,\n isLocalized,\n localeCodes,\n payload,\n}: AltTextHealthComputationArgs): Promise<AltTextHealthScan> {\n const collectionSummaries = await Promise.all(\n collections.map(async (collection): Promise<AltTextHealthScanCollection> => {\n try {\n const docs = await fetchAllDocs(payload, collection, isLocalized)\n\n return summarizeCollection({\n collection,\n docs,\n isLocalized,\n localeCodes,\n })\n } catch (error) {\n const message = error instanceof Error ? error.message : 'Unknown error'\n const collectionError = createCollectionReadError(collection, message)\n\n payload.logger.error({\n collection,\n err: error,\n msg: 'Alt text health check failed while reading a collection.',\n operation: 'find',\n plugin: ALT_TEXT_HEALTH_PLUGIN_SLUG,\n })\n\n return {\n collection,\n completeDocs: 0,\n error: collectionError,\n invalidDocIds: undefined,\n missingDocs: 0,\n partialDocs: 0,\n totalDocs: 0,\n }\n }\n }),\n )\n\n const errors = collectionSummaries\n .filter((summary) => summary.error)\n .map((summary) => summary.error!)\n\n return {\n checkedAt: new Date().toISOString(),\n collections: collectionSummaries,\n errors,\n isLocalized,\n localeCodes,\n }\n}\n\nexport const getAltTextHealthCollectionTag = (collectionSlug: string): string =>\n `${ALT_TEXT_HEALTH_GLOBAL_TAG}:${collectionSlug}`\n\nasync function getAltTextHealthScan(req: PayloadRequest): Promise<AltTextHealthScan> {\n const { payload } = req\n const pluginConfig = payload.config.custom?.altTextPluginConfig as AltTextPluginConfig | undefined\n const localeCodes =\n localesFromConfig(payload.config) ?? (pluginConfig?.locale ? [pluginConfig.locale] : [])\n const isLocalized = Boolean(payload.config.localization)\n\n if (!pluginConfig) {\n return createUnknownScan({\n error: {\n code: 'ALT_TEXT_PLUGIN_CONFIG_MISSING',\n message: 'Alt text plugin config not found',\n },\n isLocalized,\n localeCodes,\n })\n }\n\n const collections = pluginConfig.collections\n\n const cacheKeyParts = [\n ALT_TEXT_HEALTH_GLOBAL_TAG,\n [...collections].sort().join(','),\n localeCodes.join(','),\n ]\n\n const tags = [\n ALT_TEXT_HEALTH_GLOBAL_TAG,\n ...new Set(collections.map((collection) => getAltTextHealthCollectionTag(collection))),\n ]\n\n const getCachedHealthScan = createCachedAltTextHealthScan({\n cacheFactory: unstable_cache,\n cacheKeyParts,\n compute: async () =>\n computeAltTextHealthScan({\n collections,\n isLocalized,\n localeCodes,\n payload,\n }),\n revalidate: ALT_TEXT_HEALTH_CACHE_TTL,\n tags,\n })\n\n return getCachedHealthScan()\n}\n\nexport async function getAltTextHealth(req: PayloadRequest): Promise<AltTextHealthScan> {\n return getAltTextHealthScan(req)\n}\n\nexport async function getAltTextHealthWidgetData(\n req: PayloadRequest,\n): Promise<AltTextHealthWidgetData> {\n const scan = await getAltTextHealthScan(req)\n\n return {\n collections: scan.collections,\n errors: scan.errors,\n isLocalized: scan.isLocalized,\n localeCount: scan.localeCodes.length,\n totalDocs: scan.collections.reduce((total, c) => total + c.totalDocs, 0),\n }\n}\n"],"names":["unstable_cache","createCachedAltTextHealthScan","localesFromConfig","summarizeCollection","ALT_TEXT_HEALTH_PLUGIN_SLUG","ALT_TEXT_HEALTH_CACHE_TTL","ALT_TEXT_HEALTH_GLOBAL_TAG","createUnknownScan","error","isLocalized","localeCodes","checkedAt","Date","toISOString","collections","errors","createCollectionReadError","collection","message","code","operation","PAGE_SIZE","fetchAllDocs","payload","docs","page","hasMore","result","find","depth","fallbackLocale","undefined","limit","locale","overrideAccess","select","alt","doc","push","id","hasNextPage","computeAltTextHealthScan","collectionSummaries","Promise","all","map","Error","collectionError","logger","err","msg","plugin","completeDocs","invalidDocIds","missingDocs","partialDocs","totalDocs","filter","summary","getAltTextHealthCollectionTag","collectionSlug","getAltTextHealthScan","req","pluginConfig","config","custom","altTextPluginConfig","Boolean","localization","cacheKeyParts","sort","join","tags","Set","getCachedHealthScan","cacheFactory","compute","revalidate","getAltTextHealth","getAltTextHealthWidgetData","scan","localeCount","length","reduce","total","c"],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ export type AltTextHealthCacheFactory<T> = (compute: () => Promise<T>, cacheKeyParts: string[], options: {
2
+ revalidate: number;
3
+ tags: string[];
4
+ }) => () => Promise<T>;
5
+ export declare function createCachedAltTextHealthScan<T>({ cacheFactory, cacheKeyParts, compute, revalidate, tags, }: {
6
+ cacheFactory: AltTextHealthCacheFactory<T>;
7
+ cacheKeyParts: string[];
8
+ compute: () => Promise<T>;
9
+ revalidate: number;
10
+ tags: string[];
11
+ }): () => Promise<T>;
@@ -0,0 +1,8 @@
1
+ export function createCachedAltTextHealthScan({ cacheFactory, cacheKeyParts, compute, revalidate, tags }) {
2
+ return cacheFactory(compute, cacheKeyParts, {
3
+ revalidate,
4
+ tags
5
+ });
6
+ }
7
+
8
+ //# sourceMappingURL=altTextHealthCache.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/utilities/altTextHealthCache.ts"],"sourcesContent":["export type AltTextHealthCacheFactory<T> = (\n compute: () => Promise<T>,\n cacheKeyParts: string[],\n options: {\n revalidate: number\n tags: string[]\n },\n) => () => Promise<T>\n\nexport function createCachedAltTextHealthScan<T>({\n cacheFactory,\n cacheKeyParts,\n compute,\n revalidate,\n tags,\n}: {\n cacheFactory: AltTextHealthCacheFactory<T>\n cacheKeyParts: string[]\n compute: () => Promise<T>\n revalidate: number\n tags: string[]\n}): () => Promise<T> {\n return cacheFactory(compute, cacheKeyParts, {\n revalidate,\n tags,\n })\n}\n"],"names":["createCachedAltTextHealthScan","cacheFactory","cacheKeyParts","compute","revalidate","tags"],"mappings":"AASA,OAAO,SAASA,8BAAiC,EAC/CC,YAAY,EACZC,aAAa,EACbC,OAAO,EACPC,UAAU,EACVC,IAAI,EAOL;IACC,OAAOJ,aAAaE,SAASD,eAAe;QAC1CE;QACAC;IACF;AACF"}
@@ -0,0 +1,6 @@
1
+ export type AltTextHealthWidgetDisplayState = 'healthy' | 'unavailable' | 'unhealthy';
2
+ export declare function getAltTextHealthWidgetDisplayState(collection: Pick<{
3
+ error?: unknown;
4
+ missingDocs: number;
5
+ partialDocs: number;
6
+ }, 'error' | 'missingDocs' | 'partialDocs'>): AltTextHealthWidgetDisplayState;
@@ -0,0 +1,11 @@
1
+ export function getAltTextHealthWidgetDisplayState(collection) {
2
+ if (collection.error) {
3
+ return 'unavailable';
4
+ }
5
+ if (collection.missingDocs + collection.partialDocs > 0) {
6
+ return 'unhealthy';
7
+ }
8
+ return 'healthy';
9
+ }
10
+
11
+ //# sourceMappingURL=altTextHealthWidgetDisplay.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/utilities/altTextHealthWidgetDisplay.ts"],"sourcesContent":["export type AltTextHealthWidgetDisplayState = 'healthy' | 'unavailable' | 'unhealthy'\n\nexport function getAltTextHealthWidgetDisplayState(\n collection: Pick<\n { error?: unknown; missingDocs: number; partialDocs: number },\n 'error' | 'missingDocs' | 'partialDocs'\n >,\n): AltTextHealthWidgetDisplayState {\n if (collection.error) {\n return 'unavailable'\n }\n\n if (collection.missingDocs + collection.partialDocs > 0) {\n return 'unhealthy'\n }\n\n return 'healthy'\n}\n"],"names":["getAltTextHealthWidgetDisplayState","collection","error","missingDocs","partialDocs"],"mappings":"AAEA,OAAO,SAASA,mCACdC,UAGC;IAED,IAAIA,WAAWC,KAAK,EAAE;QACpB,OAAO;IACT;IAEA,IAAID,WAAWE,WAAW,GAAGF,WAAWG,WAAW,GAAG,GAAG;QACvD,OAAO;IACT;IAEA,OAAO;AACT"}
@@ -0,0 +1,2 @@
1
+ import type { SanitizedCollectionConfig } from 'payload';
2
+ export declare function getCollectionLabel(slug: string, collections: SanitizedCollectionConfig[], locale: null | string | undefined): string;
@@ -0,0 +1,17 @@
1
+ export function getCollectionLabel(slug, collections, locale) {
2
+ const collectionConfig = collections.find((c)=>c.slug === slug);
3
+ if (!collectionConfig?.labels?.plural) {
4
+ return slug;
5
+ }
6
+ const label = collectionConfig.labels.plural;
7
+ if (typeof label === 'string') {
8
+ return label;
9
+ }
10
+ if (typeof label === 'function') {
11
+ return slug;
12
+ }
13
+ const record = label;
14
+ return record[locale] ?? record[Object.keys(record)[0]] ?? slug;
15
+ }
16
+
17
+ //# sourceMappingURL=getCollectionLabel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/utilities/getCollectionLabel.ts"],"sourcesContent":["import type { SanitizedCollectionConfig } from 'payload'\n\nexport function getCollectionLabel(\n slug: string,\n collections: SanitizedCollectionConfig[],\n locale: null | string | undefined,\n): string {\n const collectionConfig = collections.find((c) => c.slug === slug)\n\n if (!collectionConfig?.labels?.plural) {\n return slug\n }\n\n const label = collectionConfig.labels.plural\n\n if (typeof label === 'string') {\n return label\n }\n\n if (typeof label === 'function') {\n return slug\n }\n\n const record = label as Record<string, string>\n\n return record[locale as string] ?? record[Object.keys(record)[0]] ?? slug\n}\n"],"names":["getCollectionLabel","slug","collections","locale","collectionConfig","find","c","labels","plural","label","record","Object","keys"],"mappings":"AAEA,OAAO,SAASA,mBACdC,IAAY,EACZC,WAAwC,EACxCC,MAAiC;IAEjC,MAAMC,mBAAmBF,YAAYG,IAAI,CAAC,CAACC,IAAMA,EAAEL,IAAI,KAAKA;IAE5D,IAAI,CAACG,kBAAkBG,QAAQC,QAAQ;QACrC,OAAOP;IACT;IAEA,MAAMQ,QAAQL,iBAAiBG,MAAM,CAACC,MAAM;IAE5C,IAAI,OAAOC,UAAU,UAAU;QAC7B,OAAOA;IACT;IAEA,IAAI,OAAOA,UAAU,YAAY;QAC/B,OAAOR;IACT;IAEA,MAAMS,SAASD;IAEf,OAAOC,MAAM,CAACP,OAAiB,IAAIO,MAAM,CAACC,OAAOC,IAAI,CAACF,OAAO,CAAC,EAAE,CAAC,IAAIT;AACvE"}
@@ -0,0 +1,17 @@
1
+ export declare const MAX_INVALID_DOC_IDS = 100;
2
+ export declare function summarizeCollection({ collection, docs, isLocalized, localeCodes, }: {
3
+ collection: string;
4
+ docs: {
5
+ alt: unknown;
6
+ id: number | string;
7
+ }[];
8
+ isLocalized: boolean;
9
+ localeCodes: string[];
10
+ }): {
11
+ collection: string;
12
+ completeDocs: number;
13
+ invalidDocIds: (string | number)[] | undefined;
14
+ missingDocs: number;
15
+ partialDocs: number;
16
+ totalDocs: number;
17
+ };
@@ -0,0 +1,62 @@
1
+ export const MAX_INVALID_DOC_IDS = 100;
2
+ const isRecord = (value)=>typeof value === 'object' && value !== null && !Array.isArray(value);
3
+ const hasAltValue = (value)=>typeof value === 'string' && value.trim().length > 0;
4
+ const countFilledLocales = (altValue, localeCodes)=>{
5
+ if (!isRecord(altValue)) {
6
+ return 0;
7
+ }
8
+ return localeCodes.filter((localeCode)=>hasAltValue(altValue[localeCode])).length;
9
+ };
10
+ export function summarizeCollection({ collection, docs, isLocalized, localeCodes }) {
11
+ let completeDocs = 0;
12
+ let missingDocs = 0;
13
+ let partialDocs = 0;
14
+ let invalidDocIds = [];
15
+ let invalidOverflow = false;
16
+ for (const doc of docs){
17
+ if (!isLocalized) {
18
+ if (hasAltValue(doc.alt)) {
19
+ completeDocs++;
20
+ } else {
21
+ missingDocs++;
22
+ if (!invalidOverflow) {
23
+ if (invalidDocIds.length < MAX_INVALID_DOC_IDS) {
24
+ invalidDocIds.push(doc.id);
25
+ } else {
26
+ invalidDocIds = undefined;
27
+ invalidOverflow = true;
28
+ }
29
+ }
30
+ }
31
+ continue;
32
+ }
33
+ const filledLocales = countFilledLocales(doc.alt, localeCodes);
34
+ if (filledLocales === localeCodes.length) {
35
+ completeDocs++;
36
+ } else {
37
+ if (filledLocales === 0) {
38
+ missingDocs++;
39
+ } else {
40
+ partialDocs++;
41
+ }
42
+ if (!invalidOverflow) {
43
+ if (invalidDocIds.length < MAX_INVALID_DOC_IDS) {
44
+ invalidDocIds.push(doc.id);
45
+ } else {
46
+ invalidDocIds = undefined;
47
+ invalidOverflow = true;
48
+ }
49
+ }
50
+ }
51
+ }
52
+ return {
53
+ collection,
54
+ completeDocs,
55
+ invalidDocIds,
56
+ missingDocs,
57
+ partialDocs,
58
+ totalDocs: docs.length
59
+ };
60
+ }
61
+
62
+ //# sourceMappingURL=summarizeCollection.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/utilities/summarizeCollection.ts"],"sourcesContent":["export const MAX_INVALID_DOC_IDS = 100\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null && !Array.isArray(value)\n\nconst hasAltValue = (value: unknown): boolean =>\n typeof value === 'string' && value.trim().length > 0\n\nconst countFilledLocales = (altValue: unknown, localeCodes: string[]): number => {\n if (!isRecord(altValue)) {\n return 0\n }\n\n return localeCodes.filter((localeCode) => hasAltValue(altValue[localeCode])).length\n}\n\nexport function summarizeCollection({\n collection,\n docs,\n isLocalized,\n localeCodes,\n}: {\n collection: string\n docs: { alt: unknown; id: number | string }[]\n isLocalized: boolean\n localeCodes: string[]\n}) {\n let completeDocs = 0\n let missingDocs = 0\n let partialDocs = 0\n let invalidDocIds: (number | string)[] | undefined = []\n let invalidOverflow = false\n\n for (const doc of docs) {\n if (!isLocalized) {\n if (hasAltValue(doc.alt)) {\n completeDocs++\n } else {\n missingDocs++\n if (!invalidOverflow) {\n if (invalidDocIds!.length < MAX_INVALID_DOC_IDS) {\n invalidDocIds!.push(doc.id)\n } else {\n invalidDocIds = undefined\n invalidOverflow = true\n }\n }\n }\n\n continue\n }\n\n const filledLocales = countFilledLocales(doc.alt, localeCodes)\n\n if (filledLocales === localeCodes.length) {\n completeDocs++\n } else {\n if (filledLocales === 0) {\n missingDocs++\n } else {\n partialDocs++\n }\n\n if (!invalidOverflow) {\n if (invalidDocIds!.length < MAX_INVALID_DOC_IDS) {\n invalidDocIds!.push(doc.id)\n } else {\n invalidDocIds = undefined\n invalidOverflow = true\n }\n }\n }\n }\n\n return {\n collection,\n completeDocs,\n invalidDocIds,\n missingDocs,\n partialDocs,\n totalDocs: docs.length,\n }\n}\n"],"names":["MAX_INVALID_DOC_IDS","isRecord","value","Array","isArray","hasAltValue","trim","length","countFilledLocales","altValue","localeCodes","filter","localeCode","summarizeCollection","collection","docs","isLocalized","completeDocs","missingDocs","partialDocs","invalidDocIds","invalidOverflow","doc","alt","push","id","undefined","filledLocales","totalDocs"],"mappings":"AAAA,OAAO,MAAMA,sBAAsB,IAAG;AAEtC,MAAMC,WAAW,CAACC,QAChB,OAAOA,UAAU,YAAYA,UAAU,QAAQ,CAACC,MAAMC,OAAO,CAACF;AAEhE,MAAMG,cAAc,CAACH,QACnB,OAAOA,UAAU,YAAYA,MAAMI,IAAI,GAAGC,MAAM,GAAG;AAErD,MAAMC,qBAAqB,CAACC,UAAmBC;IAC7C,IAAI,CAACT,SAASQ,WAAW;QACvB,OAAO;IACT;IAEA,OAAOC,YAAYC,MAAM,CAAC,CAACC,aAAeP,YAAYI,QAAQ,CAACG,WAAW,GAAGL,MAAM;AACrF;AAEA,OAAO,SAASM,oBAAoB,EAClCC,UAAU,EACVC,IAAI,EACJC,WAAW,EACXN,WAAW,EAMZ;IACC,IAAIO,eAAe;IACnB,IAAIC,cAAc;IAClB,IAAIC,cAAc;IAClB,IAAIC,gBAAiD,EAAE;IACvD,IAAIC,kBAAkB;IAEtB,KAAK,MAAMC,OAAOP,KAAM;QACtB,IAAI,CAACC,aAAa;YAChB,IAAIX,YAAYiB,IAAIC,GAAG,GAAG;gBACxBN;YACF,OAAO;gBACLC;gBACA,IAAI,CAACG,iBAAiB;oBACpB,IAAID,cAAeb,MAAM,GAAGP,qBAAqB;wBAC/CoB,cAAeI,IAAI,CAACF,IAAIG,EAAE;oBAC5B,OAAO;wBACLL,gBAAgBM;wBAChBL,kBAAkB;oBACpB;gBACF;YACF;YAEA;QACF;QAEA,MAAMM,gBAAgBnB,mBAAmBc,IAAIC,GAAG,EAAEb;QAElD,IAAIiB,kBAAkBjB,YAAYH,MAAM,EAAE;YACxCU;QACF,OAAO;YACL,IAAIU,kBAAkB,GAAG;gBACvBT;YACF,OAAO;gBACLC;YACF;YAEA,IAAI,CAACE,iBAAiB;gBACpB,IAAID,cAAeb,MAAM,GAAGP,qBAAqB;oBAC/CoB,cAAeI,IAAI,CAACF,IAAIG,EAAE;gBAC5B,OAAO;oBACLL,gBAAgBM;oBAChBL,kBAAkB;gBACpB;YACF;QACF;IACF;IAEA,OAAO;QACLP;QACAG;QACAG;QACAF;QACAC;QACAS,WAAWb,KAAKR,MAAM;IACxB;AACF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jhb.software/payload-alt-text-plugin",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
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",
@@ -14,68 +14,89 @@
14
14
  "author": "JHB Software",
15
15
  "license": "MIT",
16
16
  "type": "module",
17
- "main": "./dist/index.js",
18
- "types": "./dist/index.d.ts",
17
+ "main": "./src/index.ts",
18
+ "types": "./src/index.ts",
19
+ "scripts": {
20
+ "build": "pnpm copyfiles && pnpm build:types && pnpm build:swc",
21
+ "build:swc": "swc ./src -d ./dist --config-file .swcrc --strip-leading-paths",
22
+ "build:types": "tsc --outDir dist --rootDir ./src",
23
+ "copyfiles": "copyfiles -u 1 \"src/**/*.{html,css,scss,ttf,woff,woff2,eot,svg,jpg,png,json}\" dist/",
24
+ "clean": "rimraf --glob {dist,*.tsbuildinfo}",
25
+ "dev": "tsc -w",
26
+ "format": "prettier --write src",
27
+ "lint": "eslint src",
28
+ "lint:fix": "eslint src --fix",
29
+ "test:health": "node --experimental-strip-types --test test/altTextHealth.test.ts",
30
+ "prepublishOnly": "pnpm clean && pnpm build"
31
+ },
19
32
  "dependencies": {
20
- "openai": "^6.9.1",
33
+ "openai": "^6.33.0",
21
34
  "p-map": "^7.0.4",
22
- "zod": "^4.1.13"
35
+ "zod": "^4.3.6"
23
36
  },
24
37
  "peerDependencies": {
25
- "@payloadcms/ui": "^3.65.0",
26
- "next": "15.5.6",
27
- "payload": "^3.65.0",
28
- "react": "19.2.0",
29
- "react-dom": "19.2.0"
38
+ "@payloadcms/ui": "^3.81.0",
39
+ "next": "15.4.11",
40
+ "payload": "^3.81.0",
41
+ "react": "19.2.4",
42
+ "react-dom": "19.2.4"
30
43
  },
31
44
  "devDependencies": {
32
45
  "@payloadcms/eslint-config": "^3.28.0",
33
- "@swc/cli": "^0.7.9",
34
- "@swc/core": "^1.15.3",
35
- "@types/react": "19.2.7",
46
+ "@swc/cli": "^0.8.1",
47
+ "@swc/core": "^1.15.24",
48
+ "@types/react": "19.2.14",
36
49
  "@types/react-dom": "19.2.3",
37
50
  "copyfiles": "2.4.1",
38
- "eslint": "^9.39.1",
39
- "prettier": "^3.7.3",
40
- "rimraf": "6.1.2",
51
+ "eslint": "^9.0.0",
52
+ "prettier": "^3.8.1",
53
+ "rimraf": "6.1.3",
41
54
  "typescript": "5.9.3"
42
55
  },
43
56
  "files": [
44
57
  "dist"
45
58
  ],
46
59
  "publishConfig": {
60
+ "main": "./dist/index.js",
61
+ "types": "./dist/index.d.ts",
47
62
  "registry": "https://registry.npmjs.org/",
48
- "access": "public"
63
+ "access": "public",
64
+ "exports": {
65
+ ".": {
66
+ "import": "./dist/index.js",
67
+ "types": "./dist/index.d.ts",
68
+ "default": "./dist/index.js"
69
+ },
70
+ "./client": {
71
+ "import": "./dist/exports/client.js",
72
+ "types": "./dist/exports/client.d.ts",
73
+ "default": "./dist/exports/client.js"
74
+ },
75
+ "./server": {
76
+ "import": "./dist/exports/server.js",
77
+ "types": "./dist/exports/server.d.ts",
78
+ "default": "./dist/exports/server.js"
79
+ }
80
+ }
49
81
  },
50
82
  "exports": {
51
83
  ".": {
52
- "import": "./dist/index.js",
53
- "types": "./dist/index.d.ts",
54
- "default": "./dist/index.js"
84
+ "import": "./src/index.ts",
85
+ "types": "./src/index.ts",
86
+ "default": "./src/index.ts"
55
87
  },
56
88
  "./client": {
57
- "import": "./dist/exports/client.js",
58
- "types": "./dist/exports/client.d.ts",
59
- "default": "./dist/exports/client.js"
89
+ "import": "./src/exports/client.ts",
90
+ "types": "./src/exports/client.ts",
91
+ "default": "./src/exports/client.ts"
60
92
  },
61
93
  "./server": {
62
- "import": "./dist/exports/server.js",
63
- "types": "./dist/exports/server.d.ts",
64
- "default": "./dist/exports/server.js"
94
+ "import": "./src/exports/server.ts",
95
+ "types": "./src/exports/server.ts",
96
+ "default": "./src/exports/server.ts"
65
97
  }
66
98
  },
67
99
  "engines": {
68
100
  "node": "^18.20.2 || >=20.9.0"
69
- },
70
- "scripts": {
71
- "build": "pnpm copyfiles && pnpm build:types && pnpm build:swc",
72
- "build:swc": "swc ./src -d ./dist --config-file .swcrc --strip-leading-paths",
73
- "build:types": "tsc --outDir dist --rootDir ./src",
74
- "copyfiles": "copyfiles -u 1 \"src/**/*.{html,css,scss,ttf,woff,woff2,eot,svg,jpg,png,json}\" dist/",
75
- "clean": "rimraf --glob {dist,*.tsbuildinfo}",
76
- "dev": "tsc -w",
77
- "format": "prettier --write src",
78
- "lint": "eslint src",
79
- "lint:fix": "eslint src --fix"
80
101
  }
81
- }
102
+ }
@@ -1,5 +0,0 @@
1
- import type { PluginAltTextTranslationKeys } from 'src/translations/index.js';
2
- /** Hook which returns a translation function for the plugin translations. */
3
- export declare const usePluginTranslation: () => {
4
- t: (key: PluginAltTextTranslationKeys) => string;
5
- };
@@ -1,16 +0,0 @@
1
- import { useTranslation } from '@payloadcms/ui';
2
- /** Hook which returns a translation function for the plugin translations. */ export const usePluginTranslation = ()=>{
3
- const { i18n } = useTranslation();
4
- const pluginTranslations = i18n.translations['@jhb.software/payload-alt-text-plugin'];
5
- return {
6
- t: (key)=>{
7
- const translation = pluginTranslations[key];
8
- if (!translation) {
9
- console.error('Plugin translation not found', key);
10
- }
11
- return translation ?? key;
12
- }
13
- };
14
- };
15
-
16
- //# sourceMappingURL=usePluginTranslation.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../src/utils/usePluginTranslation.ts"],"sourcesContent":["import type {\n PluginAltTextTranslationKeys,\n PluginAltTextTranslations,\n} from 'src/translations/index.js'\n\nimport { useTranslation } from '@payloadcms/ui'\n\n/** Hook which returns a translation function for the plugin translations. */\nexport const usePluginTranslation = () => {\n const { i18n } = useTranslation<PluginAltTextTranslations, PluginAltTextTranslationKeys>()\n const pluginTranslations = i18n.translations[\n '@jhb.software/payload-alt-text-plugin'\n ] as PluginAltTextTranslations\n\n return {\n t: (key: PluginAltTextTranslationKeys) => {\n const translation = pluginTranslations[key] as string\n\n if (!translation) {\n console.error('Plugin translation not found', key)\n }\n return translation ?? key\n },\n }\n}\n"],"names":["useTranslation","usePluginTranslation","i18n","pluginTranslations","translations","t","key","translation","console","error"],"mappings":"AAKA,SAASA,cAAc,QAAQ,iBAAgB;AAE/C,2EAA2E,GAC3E,OAAO,MAAMC,uBAAuB;IAClC,MAAM,EAAEC,IAAI,EAAE,GAAGF;IACjB,MAAMG,qBAAqBD,KAAKE,YAAY,CAC1C,wCACD;IAED,OAAO;QACLC,GAAG,CAACC;YACF,MAAMC,cAAcJ,kBAAkB,CAACG,IAAI;YAE3C,IAAI,CAACC,aAAa;gBAChBC,QAAQC,KAAK,CAAC,gCAAgCH;YAChD;YACA,OAAOC,eAAeD;QACxB;IACF;AACF,EAAC"}