@inoo-ch/payload-image-optimizer 1.1.0 → 1.2.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 (48) hide show
  1. package/AGENT_DOCS.md +383 -0
  2. package/dist/components/ImageBox.d.ts +1 -15
  3. package/dist/components/ImageBox.js.map +1 -1
  4. package/dist/components/OptimizationStatus.js +84 -19
  5. package/dist/components/OptimizationStatus.js.map +1 -1
  6. package/dist/fields/imageOptimizerField.d.ts +4 -2
  7. package/dist/fields/imageOptimizerField.js +57 -54
  8. package/dist/fields/imageOptimizerField.js.map +1 -1
  9. package/dist/hooks/afterChange.js +2 -4
  10. package/dist/hooks/afterChange.js.map +1 -1
  11. package/dist/index.d.ts +2 -1
  12. package/dist/index.js +123 -105
  13. package/dist/index.js.map +1 -1
  14. package/dist/tasks/convertFormats.js +2 -4
  15. package/dist/tasks/convertFormats.js.map +1 -1
  16. package/dist/tasks/regenerateDocument.js +2 -4
  17. package/dist/tasks/regenerateDocument.js.map +1 -1
  18. package/dist/translations/index.d.ts +1 -0
  19. package/dist/translations/index.js +64 -0
  20. package/dist/translations/index.js.map +1 -0
  21. package/dist/types.d.ts +19 -1
  22. package/dist/types.js.map +1 -1
  23. package/dist/utilities/getImageOptimizerProps.d.ts +2 -10
  24. package/dist/utilities/getImageOptimizerProps.js.map +1 -1
  25. package/dist/utilities/resolveStaticDir.d.ts +3 -0
  26. package/dist/utilities/resolveStaticDir.js +10 -0
  27. package/dist/utilities/resolveStaticDir.js.map +1 -0
  28. package/package.json +36 -61
  29. package/src/components/ImageBox.tsx +65 -0
  30. package/src/components/OptimizationStatus.tsx +216 -0
  31. package/src/components/RegenerationButton.tsx +356 -0
  32. package/src/defaults.ts +36 -0
  33. package/src/endpoints/regenerate.ts +125 -0
  34. package/src/exports/client.ts +6 -0
  35. package/src/exports/rsc.ts +1 -0
  36. package/src/fields/imageOptimizerField.ts +76 -0
  37. package/src/hooks/afterChange.ts +73 -0
  38. package/src/hooks/beforeChange.ts +64 -0
  39. package/src/index.ts +124 -0
  40. package/src/next-image.d.ts +3 -0
  41. package/src/processing/index.ts +59 -0
  42. package/src/tasks/convertFormats.ts +104 -0
  43. package/src/tasks/regenerateDocument.ts +174 -0
  44. package/src/translations/index.ts +62 -0
  45. package/src/types.ts +57 -0
  46. package/src/utilities/getImageOptimizerProps.ts +50 -0
  47. package/src/utilities/resolveStaticDir.ts +12 -0
  48. package/src/utilities/thumbhash.ts +15 -0
@@ -1,75 +1,78 @@
1
- export const getImageOptimizerField = ()=>({
2
- name: 'imageOptimizer',
3
- type: 'group',
4
- admin: {
5
- position: 'sidebar',
6
- readOnly: true,
7
- components: {
8
- Field: '@inoo-ch/payload-image-optimizer/client#OptimizationStatus'
9
- }
10
- },
1
+ export const defaultImageOptimizerFields = [
2
+ {
3
+ name: 'thumbHash',
4
+ type: 'text'
5
+ },
6
+ {
7
+ name: 'originalSize',
8
+ type: 'number'
9
+ },
10
+ {
11
+ name: 'optimizedSize',
12
+ type: 'number'
13
+ },
14
+ {
15
+ name: 'status',
16
+ type: 'select',
17
+ options: [
18
+ 'pending',
19
+ 'processing',
20
+ 'complete',
21
+ 'error'
22
+ ]
23
+ },
24
+ {
25
+ name: 'error',
26
+ type: 'text'
27
+ },
28
+ {
29
+ name: 'variants',
30
+ type: 'array',
11
31
  fields: [
12
32
  {
13
- name: 'thumbHash',
33
+ name: 'format',
34
+ type: 'text'
35
+ },
36
+ {
37
+ name: 'filename',
14
38
  type: 'text'
15
39
  },
16
40
  {
17
- name: 'originalSize',
41
+ name: 'filesize',
18
42
  type: 'number'
19
43
  },
20
44
  {
21
- name: 'optimizedSize',
45
+ name: 'width',
22
46
  type: 'number'
23
47
  },
24
48
  {
25
- name: 'status',
26
- type: 'select',
27
- options: [
28
- 'pending',
29
- 'processing',
30
- 'complete',
31
- 'error'
32
- ]
49
+ name: 'height',
50
+ type: 'number'
33
51
  },
34
52
  {
35
- name: 'error',
53
+ name: 'mimeType',
36
54
  type: 'text'
37
55
  },
38
56
  {
39
- name: 'variants',
40
- type: 'array',
41
- fields: [
42
- {
43
- name: 'format',
44
- type: 'text'
45
- },
46
- {
47
- name: 'filename',
48
- type: 'text'
49
- },
50
- {
51
- name: 'filesize',
52
- type: 'number'
53
- },
54
- {
55
- name: 'width',
56
- type: 'number'
57
- },
58
- {
59
- name: 'height',
60
- type: 'number'
61
- },
62
- {
63
- name: 'mimeType',
64
- type: 'text'
65
- },
66
- {
67
- name: 'url',
68
- type: 'text'
69
- }
70
- ]
57
+ name: 'url',
58
+ type: 'text'
71
59
  }
72
60
  ]
61
+ }
62
+ ];
63
+ export const getImageOptimizerField = (fieldsOverride)=>({
64
+ name: 'imageOptimizer',
65
+ type: 'group',
66
+ admin: {
67
+ position: 'sidebar',
68
+ readOnly: true,
69
+ components: {
70
+ Field: '@inoo-ch/payload-image-optimizer/client#OptimizationStatus'
71
+ }
72
+ },
73
+ fields: fieldsOverride ? fieldsOverride({
74
+ defaultFields: defaultImageOptimizerFields
75
+ }) : defaultImageOptimizerFields
73
76
  });
74
77
 
75
78
  //# sourceMappingURL=imageOptimizerField.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/fields/imageOptimizerField.ts"],"sourcesContent":["import type { GroupField } from 'payload'\n\nexport const getImageOptimizerField = (): GroupField => ({\n name: 'imageOptimizer',\n type: 'group',\n admin: {\n position: 'sidebar',\n readOnly: true,\n components: {\n Field: '@inoo-ch/payload-image-optimizer/client#OptimizationStatus',\n },\n },\n fields: [\n {\n name: 'thumbHash',\n type: 'text',\n },\n {\n name: 'originalSize',\n type: 'number',\n },\n {\n name: 'optimizedSize',\n type: 'number',\n },\n {\n name: 'status',\n type: 'select',\n options: ['pending', 'processing', 'complete', 'error'],\n },\n {\n name: 'error',\n type: 'text',\n },\n {\n name: 'variants',\n type: 'array',\n fields: [\n {\n name: 'format',\n type: 'text',\n },\n {\n name: 'filename',\n type: 'text',\n },\n {\n name: 'filesize',\n type: 'number',\n },\n {\n name: 'width',\n type: 'number',\n },\n {\n name: 'height',\n type: 'number',\n },\n {\n name: 'mimeType',\n type: 'text',\n },\n {\n name: 'url',\n type: 'text',\n },\n ],\n },\n ],\n})\n"],"names":["getImageOptimizerField","name","type","admin","position","readOnly","components","Field","fields","options"],"mappings":"AAEA,OAAO,MAAMA,yBAAyB,IAAmB,CAAA;QACvDC,MAAM;QACNC,MAAM;QACNC,OAAO;YACLC,UAAU;YACVC,UAAU;YACVC,YAAY;gBACVC,OAAO;YACT;QACF;QACAC,QAAQ;YACN;gBACEP,MAAM;gBACNC,MAAM;YACR;YACA;gBACED,MAAM;gBACNC,MAAM;YACR;YACA;gBACED,MAAM;gBACNC,MAAM;YACR;YACA;gBACED,MAAM;gBACNC,MAAM;gBACNO,SAAS;oBAAC;oBAAW;oBAAc;oBAAY;iBAAQ;YACzD;YACA;gBACER,MAAM;gBACNC,MAAM;YACR;YACA;gBACED,MAAM;gBACNC,MAAM;gBACNM,QAAQ;oBACN;wBACEP,MAAM;wBACNC,MAAM;oBACR;oBACA;wBACED,MAAM;wBACNC,MAAM;oBACR;oBACA;wBACED,MAAM;wBACNC,MAAM;oBACR;oBACA;wBACED,MAAM;wBACNC,MAAM;oBACR;oBACA;wBACED,MAAM;wBACNC,MAAM;oBACR;oBACA;wBACED,MAAM;wBACNC,MAAM;oBACR;oBACA;wBACED,MAAM;wBACNC,MAAM;oBACR;iBACD;YACH;SACD;IACH,CAAA,EAAE"}
1
+ {"version":3,"sources":["../../src/fields/imageOptimizerField.ts"],"sourcesContent":["import type { Field, GroupField } from 'payload'\n\nimport type { FieldsOverride } from '../types.js'\n\nexport const defaultImageOptimizerFields: Field[] = [\n {\n name: 'thumbHash',\n type: 'text',\n },\n {\n name: 'originalSize',\n type: 'number',\n },\n {\n name: 'optimizedSize',\n type: 'number',\n },\n {\n name: 'status',\n type: 'select',\n options: ['pending', 'processing', 'complete', 'error'],\n },\n {\n name: 'error',\n type: 'text',\n },\n {\n name: 'variants',\n type: 'array',\n fields: [\n {\n name: 'format',\n type: 'text',\n },\n {\n name: 'filename',\n type: 'text',\n },\n {\n name: 'filesize',\n type: 'number',\n },\n {\n name: 'width',\n type: 'number',\n },\n {\n name: 'height',\n type: 'number',\n },\n {\n name: 'mimeType',\n type: 'text',\n },\n {\n name: 'url',\n type: 'text',\n },\n ],\n },\n]\n\nexport const getImageOptimizerField = (fieldsOverride?: FieldsOverride): GroupField => ({\n name: 'imageOptimizer',\n type: 'group',\n admin: {\n position: 'sidebar',\n readOnly: true,\n components: {\n Field: '@inoo-ch/payload-image-optimizer/client#OptimizationStatus',\n },\n },\n fields: fieldsOverride\n ? fieldsOverride({ defaultFields: defaultImageOptimizerFields })\n : defaultImageOptimizerFields,\n})\n"],"names":["defaultImageOptimizerFields","name","type","options","fields","getImageOptimizerField","fieldsOverride","admin","position","readOnly","components","Field","defaultFields"],"mappings":"AAIA,OAAO,MAAMA,8BAAuC;IAClD;QACEC,MAAM;QACNC,MAAM;IACR;IACA;QACED,MAAM;QACNC,MAAM;IACR;IACA;QACED,MAAM;QACNC,MAAM;IACR;IACA;QACED,MAAM;QACNC,MAAM;QACNC,SAAS;YAAC;YAAW;YAAc;YAAY;SAAQ;IACzD;IACA;QACEF,MAAM;QACNC,MAAM;IACR;IACA;QACED,MAAM;QACNC,MAAM;QACNE,QAAQ;YACN;gBACEH,MAAM;gBACNC,MAAM;YACR;YACA;gBACED,MAAM;gBACNC,MAAM;YACR;YACA;gBACED,MAAM;gBACNC,MAAM;YACR;YACA;gBACED,MAAM;gBACNC,MAAM;YACR;YACA;gBACED,MAAM;gBACNC,MAAM;YACR;YACA;gBACED,MAAM;gBACNC,MAAM;YACR;YACA;gBACED,MAAM;gBACNC,MAAM;YACR;SACD;IACH;CACD,CAAA;AAED,OAAO,MAAMG,yBAAyB,CAACC,iBAAiD,CAAA;QACtFL,MAAM;QACNC,MAAM;QACNK,OAAO;YACLC,UAAU;YACVC,UAAU;YACVC,YAAY;gBACVC,OAAO;YACT;QACF;QACAP,QAAQE,iBACJA,eAAe;YAAEM,eAAeZ;QAA4B,KAC5DA;IACN,CAAA,EAAE"}
@@ -1,15 +1,13 @@
1
1
  import fs from 'fs/promises';
2
2
  import path from 'path';
3
3
  import { resolveCollectionConfig } from '../defaults.js';
4
+ import { resolveStaticDir } from '../utilities/resolveStaticDir.js';
4
5
  export const createAfterChangeHook = (resolvedConfig, collectionSlug)=>{
5
6
  return async ({ context, doc, req })=>{
6
7
  if (context?.imageOptimizer_skip) return doc;
7
8
  if (!req.file || !req.file.data || !req.file.mimetype?.startsWith('image/')) return doc;
8
9
  const collectionConfig = req.payload.collections[collectionSlug].config;
9
- let staticDir = typeof collectionConfig.upload === 'object' ? collectionConfig.upload.staticDir || '' : '';
10
- if (staticDir && !path.isAbsolute(staticDir)) {
11
- staticDir = path.resolve(process.cwd(), staticDir);
12
- }
10
+ const staticDir = resolveStaticDir(collectionConfig);
13
11
  const perCollectionConfig = resolveCollectionConfig(resolvedConfig, collectionSlug);
14
12
  // Overwrite the file on disk with the processed (stripped/resized/converted) buffer
15
13
  // Payload 3.0 writes the original buffer to disk; we replace it here
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/hooks/afterChange.ts"],"sourcesContent":["import fs from 'fs/promises'\nimport path from 'path'\nimport type { CollectionAfterChangeHook } from 'payload'\n\nimport type { ResolvedImageOptimizerConfig } from '../types.js'\nimport { resolveCollectionConfig } from '../defaults.js'\n\nexport const createAfterChangeHook = (\n resolvedConfig: ResolvedImageOptimizerConfig,\n collectionSlug: string,\n): CollectionAfterChangeHook => {\n return async ({ context, doc, req }) => {\n if (context?.imageOptimizer_skip) return doc\n\n if (!req.file || !req.file.data || !req.file.mimetype?.startsWith('image/')) return doc\n\n const collectionConfig = req.payload.collections[collectionSlug as keyof typeof req.payload.collections].config\n let staticDir: string =\n typeof collectionConfig.upload === 'object' ? collectionConfig.upload.staticDir || '' : ''\n\n if (staticDir && !path.isAbsolute(staticDir)) {\n staticDir = path.resolve(process.cwd(), staticDir)\n }\n\n const perCollectionConfig = resolveCollectionConfig(resolvedConfig, collectionSlug)\n\n // Overwrite the file on disk with the processed (stripped/resized/converted) buffer\n // Payload 3.0 writes the original buffer to disk; we replace it here\n const processedBuffer = context.imageOptimizer_processedBuffer as Buffer | undefined\n if (processedBuffer && doc.filename && staticDir) {\n const safeFilename = path.basename(doc.filename as string)\n const filePath = path.join(staticDir, safeFilename)\n await fs.writeFile(filePath, processedBuffer)\n\n // If replaceOriginal changed the filename, clean up the old file Payload wrote\n const originalFilename = context.imageOptimizer_originalFilename as string | undefined\n if (originalFilename && originalFilename !== safeFilename) {\n const oldFilePath = path.join(staticDir, path.basename(originalFilename))\n await fs.unlink(oldFilePath).catch(() => {\n // Old file may not exist if Payload used the new filename\n })\n }\n }\n\n // When replaceOriginal is on and only one format is configured, the main file\n // is already converted — skip the async job and mark complete immediately.\n if (perCollectionConfig.replaceOriginal && perCollectionConfig.formats.length <= 1) {\n await req.payload.update({\n collection: collectionSlug,\n id: doc.id,\n data: {\n imageOptimizer: {\n status: 'complete',\n variants: [],\n },\n },\n context: { imageOptimizer_skip: true },\n })\n return doc\n }\n\n // Queue async format conversion job for remaining variants\n await req.payload.jobs.queue({\n task: 'imageOptimizer_convertFormats',\n input: {\n collectionSlug,\n docId: String(doc.id),\n },\n })\n\n req.payload.jobs.run().catch((err: unknown) => {\n req.payload.logger.error({ err }, 'Image optimizer job runner failed')\n })\n\n return doc\n }\n}\n"],"names":["fs","path","resolveCollectionConfig","createAfterChangeHook","resolvedConfig","collectionSlug","context","doc","req","imageOptimizer_skip","file","data","mimetype","startsWith","collectionConfig","payload","collections","config","staticDir","upload","isAbsolute","resolve","process","cwd","perCollectionConfig","processedBuffer","imageOptimizer_processedBuffer","filename","safeFilename","basename","filePath","join","writeFile","originalFilename","imageOptimizer_originalFilename","oldFilePath","unlink","catch","replaceOriginal","formats","length","update","collection","id","imageOptimizer","status","variants","jobs","queue","task","input","docId","String","run","err","logger","error"],"mappings":"AAAA,OAAOA,QAAQ,cAAa;AAC5B,OAAOC,UAAU,OAAM;AAIvB,SAASC,uBAAuB,QAAQ,iBAAgB;AAExD,OAAO,MAAMC,wBAAwB,CACnCC,gBACAC;IAEA,OAAO,OAAO,EAAEC,OAAO,EAAEC,GAAG,EAAEC,GAAG,EAAE;QACjC,IAAIF,SAASG,qBAAqB,OAAOF;QAEzC,IAAI,CAACC,IAAIE,IAAI,IAAI,CAACF,IAAIE,IAAI,CAACC,IAAI,IAAI,CAACH,IAAIE,IAAI,CAACE,QAAQ,EAAEC,WAAW,WAAW,OAAON;QAEpF,MAAMO,mBAAmBN,IAAIO,OAAO,CAACC,WAAW,CAACX,eAAuD,CAACY,MAAM;QAC/G,IAAIC,YACF,OAAOJ,iBAAiBK,MAAM,KAAK,WAAWL,iBAAiBK,MAAM,CAACD,SAAS,IAAI,KAAK;QAE1F,IAAIA,aAAa,CAACjB,KAAKmB,UAAU,CAACF,YAAY;YAC5CA,YAAYjB,KAAKoB,OAAO,CAACC,QAAQC,GAAG,IAAIL;QAC1C;QAEA,MAAMM,sBAAsBtB,wBAAwBE,gBAAgBC;QAEpE,oFAAoF;QACpF,qEAAqE;QACrE,MAAMoB,kBAAkBnB,QAAQoB,8BAA8B;QAC9D,IAAID,mBAAmBlB,IAAIoB,QAAQ,IAAIT,WAAW;YAChD,MAAMU,eAAe3B,KAAK4B,QAAQ,CAACtB,IAAIoB,QAAQ;YAC/C,MAAMG,WAAW7B,KAAK8B,IAAI,CAACb,WAAWU;YACtC,MAAM5B,GAAGgC,SAAS,CAACF,UAAUL;YAE7B,+EAA+E;YAC/E,MAAMQ,mBAAmB3B,QAAQ4B,+BAA+B;YAChE,IAAID,oBAAoBA,qBAAqBL,cAAc;gBACzD,MAAMO,cAAclC,KAAK8B,IAAI,CAACb,WAAWjB,KAAK4B,QAAQ,CAACI;gBACvD,MAAMjC,GAAGoC,MAAM,CAACD,aAAaE,KAAK,CAAC;gBACjC,0DAA0D;gBAC5D;YACF;QACF;QAEA,8EAA8E;QAC9E,2EAA2E;QAC3E,IAAIb,oBAAoBc,eAAe,IAAId,oBAAoBe,OAAO,CAACC,MAAM,IAAI,GAAG;YAClF,MAAMhC,IAAIO,OAAO,CAAC0B,MAAM,CAAC;gBACvBC,YAAYrC;gBACZsC,IAAIpC,IAAIoC,EAAE;gBACVhC,MAAM;oBACJiC,gBAAgB;wBACdC,QAAQ;wBACRC,UAAU,EAAE;oBACd;gBACF;gBACAxC,SAAS;oBAAEG,qBAAqB;gBAAK;YACvC;YACA,OAAOF;QACT;QAEA,2DAA2D;QAC3D,MAAMC,IAAIO,OAAO,CAACgC,IAAI,CAACC,KAAK,CAAC;YAC3BC,MAAM;YACNC,OAAO;gBACL7C;gBACA8C,OAAOC,OAAO7C,IAAIoC,EAAE;YACtB;QACF;QAEAnC,IAAIO,OAAO,CAACgC,IAAI,CAACM,GAAG,GAAGhB,KAAK,CAAC,CAACiB;YAC5B9C,IAAIO,OAAO,CAACwC,MAAM,CAACC,KAAK,CAAC;gBAAEF;YAAI,GAAG;QACpC;QAEA,OAAO/C;IACT;AACF,EAAC"}
1
+ {"version":3,"sources":["../../src/hooks/afterChange.ts"],"sourcesContent":["import fs from 'fs/promises'\nimport path from 'path'\nimport type { CollectionAfterChangeHook } from 'payload'\n\nimport type { ResolvedImageOptimizerConfig } from '../types.js'\nimport { resolveCollectionConfig } from '../defaults.js'\nimport { resolveStaticDir } from '../utilities/resolveStaticDir.js'\n\nexport const createAfterChangeHook = (\n resolvedConfig: ResolvedImageOptimizerConfig,\n collectionSlug: string,\n): CollectionAfterChangeHook => {\n return async ({ context, doc, req }) => {\n if (context?.imageOptimizer_skip) return doc\n\n if (!req.file || !req.file.data || !req.file.mimetype?.startsWith('image/')) return doc\n\n const collectionConfig = req.payload.collections[collectionSlug as keyof typeof req.payload.collections].config\n const staticDir = resolveStaticDir(collectionConfig)\n\n const perCollectionConfig = resolveCollectionConfig(resolvedConfig, collectionSlug)\n\n // Overwrite the file on disk with the processed (stripped/resized/converted) buffer\n // Payload 3.0 writes the original buffer to disk; we replace it here\n const processedBuffer = context.imageOptimizer_processedBuffer as Buffer | undefined\n if (processedBuffer && doc.filename && staticDir) {\n const safeFilename = path.basename(doc.filename as string)\n const filePath = path.join(staticDir, safeFilename)\n await fs.writeFile(filePath, processedBuffer)\n\n // If replaceOriginal changed the filename, clean up the old file Payload wrote\n const originalFilename = context.imageOptimizer_originalFilename as string | undefined\n if (originalFilename && originalFilename !== safeFilename) {\n const oldFilePath = path.join(staticDir, path.basename(originalFilename))\n await fs.unlink(oldFilePath).catch(() => {\n // Old file may not exist if Payload used the new filename\n })\n }\n }\n\n // When replaceOriginal is on and only one format is configured, the main file\n // is already converted — skip the async job and mark complete immediately.\n if (perCollectionConfig.replaceOriginal && perCollectionConfig.formats.length <= 1) {\n await req.payload.update({\n collection: collectionSlug,\n id: doc.id,\n data: {\n imageOptimizer: {\n status: 'complete',\n variants: [],\n },\n },\n context: { imageOptimizer_skip: true },\n })\n return doc\n }\n\n // Queue async format conversion job for remaining variants\n await req.payload.jobs.queue({\n task: 'imageOptimizer_convertFormats',\n input: {\n collectionSlug,\n docId: String(doc.id),\n },\n })\n\n req.payload.jobs.run().catch((err: unknown) => {\n req.payload.logger.error({ err }, 'Image optimizer job runner failed')\n })\n\n return doc\n }\n}\n"],"names":["fs","path","resolveCollectionConfig","resolveStaticDir","createAfterChangeHook","resolvedConfig","collectionSlug","context","doc","req","imageOptimizer_skip","file","data","mimetype","startsWith","collectionConfig","payload","collections","config","staticDir","perCollectionConfig","processedBuffer","imageOptimizer_processedBuffer","filename","safeFilename","basename","filePath","join","writeFile","originalFilename","imageOptimizer_originalFilename","oldFilePath","unlink","catch","replaceOriginal","formats","length","update","collection","id","imageOptimizer","status","variants","jobs","queue","task","input","docId","String","run","err","logger","error"],"mappings":"AAAA,OAAOA,QAAQ,cAAa;AAC5B,OAAOC,UAAU,OAAM;AAIvB,SAASC,uBAAuB,QAAQ,iBAAgB;AACxD,SAASC,gBAAgB,QAAQ,mCAAkC;AAEnE,OAAO,MAAMC,wBAAwB,CACnCC,gBACAC;IAEA,OAAO,OAAO,EAAEC,OAAO,EAAEC,GAAG,EAAEC,GAAG,EAAE;QACjC,IAAIF,SAASG,qBAAqB,OAAOF;QAEzC,IAAI,CAACC,IAAIE,IAAI,IAAI,CAACF,IAAIE,IAAI,CAACC,IAAI,IAAI,CAACH,IAAIE,IAAI,CAACE,QAAQ,EAAEC,WAAW,WAAW,OAAON;QAEpF,MAAMO,mBAAmBN,IAAIO,OAAO,CAACC,WAAW,CAACX,eAAuD,CAACY,MAAM;QAC/G,MAAMC,YAAYhB,iBAAiBY;QAEnC,MAAMK,sBAAsBlB,wBAAwBG,gBAAgBC;QAEpE,oFAAoF;QACpF,qEAAqE;QACrE,MAAMe,kBAAkBd,QAAQe,8BAA8B;QAC9D,IAAID,mBAAmBb,IAAIe,QAAQ,IAAIJ,WAAW;YAChD,MAAMK,eAAevB,KAAKwB,QAAQ,CAACjB,IAAIe,QAAQ;YAC/C,MAAMG,WAAWzB,KAAK0B,IAAI,CAACR,WAAWK;YACtC,MAAMxB,GAAG4B,SAAS,CAACF,UAAUL;YAE7B,+EAA+E;YAC/E,MAAMQ,mBAAmBtB,QAAQuB,+BAA+B;YAChE,IAAID,oBAAoBA,qBAAqBL,cAAc;gBACzD,MAAMO,cAAc9B,KAAK0B,IAAI,CAACR,WAAWlB,KAAKwB,QAAQ,CAACI;gBACvD,MAAM7B,GAAGgC,MAAM,CAACD,aAAaE,KAAK,CAAC;gBACjC,0DAA0D;gBAC5D;YACF;QACF;QAEA,8EAA8E;QAC9E,2EAA2E;QAC3E,IAAIb,oBAAoBc,eAAe,IAAId,oBAAoBe,OAAO,CAACC,MAAM,IAAI,GAAG;YAClF,MAAM3B,IAAIO,OAAO,CAACqB,MAAM,CAAC;gBACvBC,YAAYhC;gBACZiC,IAAI/B,IAAI+B,EAAE;gBACV3B,MAAM;oBACJ4B,gBAAgB;wBACdC,QAAQ;wBACRC,UAAU,EAAE;oBACd;gBACF;gBACAnC,SAAS;oBAAEG,qBAAqB;gBAAK;YACvC;YACA,OAAOF;QACT;QAEA,2DAA2D;QAC3D,MAAMC,IAAIO,OAAO,CAAC2B,IAAI,CAACC,KAAK,CAAC;YAC3BC,MAAM;YACNC,OAAO;gBACLxC;gBACAyC,OAAOC,OAAOxC,IAAI+B,EAAE;YACtB;QACF;QAEA9B,IAAIO,OAAO,CAAC2B,IAAI,CAACM,GAAG,GAAGhB,KAAK,CAAC,CAACiB;YAC5BzC,IAAIO,OAAO,CAACmC,MAAM,CAACC,KAAK,CAAC;gBAAEF;YAAI,GAAG;QACpC;QAEA,OAAO1C;IACT;AACF,EAAC"}
package/dist/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import type { Config } from 'payload';
2
2
  import type { ImageOptimizerConfig } from './types.js';
3
- export type { ImageOptimizerConfig, ImageFormat, FormatQuality, CollectionOptimizerConfig } from './types.js';
3
+ export type { ImageOptimizerConfig, ImageFormat, FormatQuality, CollectionOptimizerConfig, ImageOptimizerData, MediaResource, FieldsOverride } from './types.js';
4
+ export { defaultImageOptimizerFields } from './fields/imageOptimizerField.js';
4
5
  export { encodeImageToThumbHash, decodeThumbHashToDataURL } from './utilities/thumbhash.js';
5
6
  export declare const imageOptimizer: (pluginOptions: ImageOptimizerConfig) => (config: Config) => Config;
package/dist/index.js CHANGED
@@ -1,127 +1,145 @@
1
+ import { deepMergeSimple } from 'payload/shared';
1
2
  import { resolveConfig } from './defaults.js';
3
+ import { translations } from './translations/index.js';
2
4
  import { getImageOptimizerField } from './fields/imageOptimizerField.js';
3
5
  import { createBeforeChangeHook } from './hooks/beforeChange.js';
4
6
  import { createAfterChangeHook } from './hooks/afterChange.js';
5
7
  import { createConvertFormatsHandler } from './tasks/convertFormats.js';
6
8
  import { createRegenerateDocumentHandler } from './tasks/regenerateDocument.js';
7
9
  import { createRegenerateHandler, createRegenerateStatusHandler } from './endpoints/regenerate.js';
10
+ export { defaultImageOptimizerFields } from './fields/imageOptimizerField.js';
8
11
  export { encodeImageToThumbHash, decodeThumbHashToDataURL } from './utilities/thumbhash.js';
9
12
  export const imageOptimizer = (pluginOptions)=>(config)=>{
10
13
  const resolvedConfig = resolveConfig(pluginOptions);
11
- if (!config.collections) {
12
- config.collections = [];
13
- }
14
- // Inject imageOptimizer fields into targeted upload collections
15
- for(const collectionSlug in resolvedConfig.collections){
16
- const collection = config.collections.find((c)=>c.slug === collectionSlug);
17
- if (collection) {
18
- collection.fields.push(getImageOptimizerField());
14
+ const targetSlugs = Object.keys(resolvedConfig.collections);
15
+ // Inject fields (and hooks when enabled) into targeted upload collections
16
+ const collections = (config.collections || []).map((collection)=>{
17
+ if (!targetSlugs.includes(collection.slug)) {
18
+ return collection;
19
19
  }
20
- }
21
- // If disabled, keep fields for schema consistency but skip hooks/tasks
22
- if (resolvedConfig.disabled) {
23
- return config;
24
- }
25
- // Inject hooks into targeted upload collections
26
- for(const collectionSlug in resolvedConfig.collections){
27
- const collection = config.collections.find((c)=>c.slug === collectionSlug);
28
- if (collection) {
29
- if (!collection.hooks) {
30
- collection.hooks = {};
31
- }
32
- if (!collection.hooks.beforeChange) {
33
- collection.hooks.beforeChange = [];
34
- }
35
- collection.hooks.beforeChange.push(createBeforeChangeHook(resolvedConfig, collectionSlug));
36
- if (!collection.hooks.afterChange) {
37
- collection.hooks.afterChange = [];
38
- }
39
- collection.hooks.afterChange.push(createAfterChangeHook(resolvedConfig, collectionSlug));
40
- // Add RegenerationButton to the collection list view
41
- if (!collection.admin) {
42
- collection.admin = {};
43
- }
44
- if (!collection.admin.components) {
45
- collection.admin.components = {};
46
- }
47
- if (!collection.admin.components.beforeListTable) {
48
- collection.admin.components.beforeListTable = [];
49
- }
50
- collection.admin.components.beforeListTable.push('@inoo-ch/payload-image-optimizer/client#RegenerationButton');
20
+ // Always inject fields for schema consistency (even when disabled)
21
+ const fields = [
22
+ ...collection.fields,
23
+ getImageOptimizerField(pluginOptions.fieldsOverride)
24
+ ];
25
+ if (resolvedConfig.disabled) {
26
+ return {
27
+ ...collection,
28
+ fields
29
+ };
51
30
  }
52
- }
53
- // Register async format conversion job task
54
- if (!config.jobs) {
55
- config.jobs = {
56
- tasks: []
57
- };
58
- }
59
- if (!config.jobs.tasks) {
60
- config.jobs.tasks = [];
61
- }
62
- config.jobs.tasks.push({
63
- slug: 'imageOptimizer_convertFormats',
64
- inputSchema: [
65
- {
66
- name: 'collectionSlug',
67
- type: 'text',
68
- required: true
31
+ return {
32
+ ...collection,
33
+ fields,
34
+ hooks: {
35
+ ...collection.hooks,
36
+ beforeChange: [
37
+ ...collection.hooks?.beforeChange || [],
38
+ createBeforeChangeHook(resolvedConfig, collection.slug)
39
+ ],
40
+ afterChange: [
41
+ ...collection.hooks?.afterChange || [],
42
+ createAfterChangeHook(resolvedConfig, collection.slug)
43
+ ]
69
44
  },
70
- {
71
- name: 'docId',
72
- type: 'text',
73
- required: true
74
- }
75
- ],
76
- outputSchema: [
77
- {
78
- name: 'variantsGenerated',
79
- type: 'number'
45
+ admin: {
46
+ ...collection.admin,
47
+ components: {
48
+ ...collection.admin?.components,
49
+ beforeListTable: [
50
+ ...collection.admin?.components?.beforeListTable || [],
51
+ '@inoo-ch/payload-image-optimizer/client#RegenerationButton'
52
+ ]
53
+ }
80
54
  }
81
- ],
82
- retries: 2,
83
- handler: createConvertFormatsHandler(resolvedConfig)
55
+ };
84
56
  });
85
- config.jobs.tasks.push({
86
- slug: 'imageOptimizer_regenerateDocument',
87
- inputSchema: [
88
- {
89
- name: 'collectionSlug',
90
- type: 'text',
91
- required: true
92
- },
93
- {
94
- name: 'docId',
95
- type: 'text',
96
- required: true
97
- }
98
- ],
99
- outputSchema: [
57
+ const i18n = {
58
+ ...config.i18n,
59
+ translations: deepMergeSimple(translations, config.i18n?.translations ?? {})
60
+ };
61
+ // If disabled, return with fields injected but no tasks/endpoints
62
+ if (resolvedConfig.disabled) {
63
+ return {
64
+ ...config,
65
+ collections,
66
+ i18n
67
+ };
68
+ }
69
+ return {
70
+ ...config,
71
+ collections,
72
+ i18n,
73
+ jobs: {
74
+ ...config.jobs,
75
+ tasks: [
76
+ ...config.jobs?.tasks || [],
77
+ {
78
+ slug: 'imageOptimizer_convertFormats',
79
+ inputSchema: [
80
+ {
81
+ name: 'collectionSlug',
82
+ type: 'text',
83
+ required: true
84
+ },
85
+ {
86
+ name: 'docId',
87
+ type: 'text',
88
+ required: true
89
+ }
90
+ ],
91
+ outputSchema: [
92
+ {
93
+ name: 'variantsGenerated',
94
+ type: 'number'
95
+ }
96
+ ],
97
+ retries: 2,
98
+ handler: createConvertFormatsHandler(resolvedConfig)
99
+ },
100
+ {
101
+ slug: 'imageOptimizer_regenerateDocument',
102
+ inputSchema: [
103
+ {
104
+ name: 'collectionSlug',
105
+ type: 'text',
106
+ required: true
107
+ },
108
+ {
109
+ name: 'docId',
110
+ type: 'text',
111
+ required: true
112
+ }
113
+ ],
114
+ outputSchema: [
115
+ {
116
+ name: 'status',
117
+ type: 'text'
118
+ },
119
+ {
120
+ name: 'reason',
121
+ type: 'text'
122
+ }
123
+ ],
124
+ retries: 2,
125
+ handler: createRegenerateDocumentHandler(resolvedConfig)
126
+ }
127
+ ]
128
+ },
129
+ endpoints: [
130
+ ...config.endpoints ?? [],
100
131
  {
101
- name: 'status',
102
- type: 'text'
132
+ path: '/image-optimizer/regenerate',
133
+ method: 'post',
134
+ handler: createRegenerateHandler(resolvedConfig)
103
135
  },
104
136
  {
105
- name: 'reason',
106
- type: 'text'
137
+ path: '/image-optimizer/regenerate',
138
+ method: 'get',
139
+ handler: createRegenerateStatusHandler(resolvedConfig)
107
140
  }
108
- ],
109
- retries: 2,
110
- handler: createRegenerateDocumentHandler(resolvedConfig)
111
- });
112
- // Register regeneration endpoints
113
- if (!config.endpoints) config.endpoints = [];
114
- config.endpoints.push({
115
- path: '/image-optimizer/regenerate',
116
- method: 'post',
117
- handler: createRegenerateHandler(resolvedConfig)
118
- });
119
- config.endpoints.push({
120
- path: '/image-optimizer/regenerate',
121
- method: 'get',
122
- handler: createRegenerateStatusHandler(resolvedConfig)
123
- });
124
- return config;
141
+ ]
142
+ };
125
143
  };
126
144
 
127
145
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import type { Config } from 'payload'\n\nimport type { ImageOptimizerConfig } from './types.js'\nimport { resolveConfig } from './defaults.js'\nimport { getImageOptimizerField } from './fields/imageOptimizerField.js'\nimport { createBeforeChangeHook } from './hooks/beforeChange.js'\nimport { createAfterChangeHook } from './hooks/afterChange.js'\nimport { createConvertFormatsHandler } from './tasks/convertFormats.js'\nimport { createRegenerateDocumentHandler } from './tasks/regenerateDocument.js'\nimport { createRegenerateHandler, createRegenerateStatusHandler } from './endpoints/regenerate.js'\n\nexport type { ImageOptimizerConfig, ImageFormat, FormatQuality, CollectionOptimizerConfig } from './types.js'\n\nexport { encodeImageToThumbHash, decodeThumbHashToDataURL } from './utilities/thumbhash.js'\n\nexport const imageOptimizer =\n (pluginOptions: ImageOptimizerConfig) =>\n (config: Config): Config => {\n const resolvedConfig = resolveConfig(pluginOptions)\n\n if (!config.collections) {\n config.collections = []\n }\n\n // Inject imageOptimizer fields into targeted upload collections\n for (const collectionSlug in resolvedConfig.collections) {\n const collection = config.collections.find((c) => c.slug === collectionSlug)\n\n if (collection) {\n collection.fields.push(getImageOptimizerField())\n }\n }\n\n // If disabled, keep fields for schema consistency but skip hooks/tasks\n if (resolvedConfig.disabled) {\n return config\n }\n\n // Inject hooks into targeted upload collections\n for (const collectionSlug in resolvedConfig.collections) {\n const collection = config.collections.find((c) => c.slug === collectionSlug)\n\n if (collection) {\n if (!collection.hooks) {\n collection.hooks = {}\n }\n\n if (!collection.hooks.beforeChange) {\n collection.hooks.beforeChange = []\n }\n collection.hooks.beforeChange.push(createBeforeChangeHook(resolvedConfig, collectionSlug))\n\n if (!collection.hooks.afterChange) {\n collection.hooks.afterChange = []\n }\n collection.hooks.afterChange.push(createAfterChangeHook(resolvedConfig, collectionSlug))\n\n // Add RegenerationButton to the collection list view\n if (!collection.admin) {\n collection.admin = {}\n }\n if (!collection.admin.components) {\n collection.admin.components = {}\n }\n if (!collection.admin.components.beforeListTable) {\n collection.admin.components.beforeListTable = []\n }\n collection.admin.components.beforeListTable.push(\n '@inoo-ch/payload-image-optimizer/client#RegenerationButton',\n )\n }\n }\n\n // Register async format conversion job task\n if (!config.jobs) {\n config.jobs = { tasks: [] }\n }\n if (!config.jobs!.tasks) {\n config.jobs!.tasks = []\n }\n\n config.jobs!.tasks!.push({\n slug: 'imageOptimizer_convertFormats',\n inputSchema: [\n { name: 'collectionSlug', type: 'text', required: true },\n { name: 'docId', type: 'text', required: true },\n ],\n outputSchema: [\n { name: 'variantsGenerated', type: 'number' },\n ],\n retries: 2,\n handler: createConvertFormatsHandler(resolvedConfig),\n } as any)\n\n config.jobs!.tasks!.push({\n slug: 'imageOptimizer_regenerateDocument',\n inputSchema: [\n { name: 'collectionSlug', type: 'text', required: true },\n { name: 'docId', type: 'text', required: true },\n ],\n outputSchema: [\n { name: 'status', type: 'text' },\n { name: 'reason', type: 'text' },\n ],\n retries: 2,\n handler: createRegenerateDocumentHandler(resolvedConfig),\n } as any)\n\n // Register regeneration endpoints\n if (!config.endpoints) config.endpoints = []\n\n config.endpoints.push({\n path: '/image-optimizer/regenerate',\n method: 'post',\n handler: createRegenerateHandler(resolvedConfig),\n })\n\n config.endpoints.push({\n path: '/image-optimizer/regenerate',\n method: 'get',\n handler: createRegenerateStatusHandler(resolvedConfig),\n })\n\n return config\n }\n"],"names":["resolveConfig","getImageOptimizerField","createBeforeChangeHook","createAfterChangeHook","createConvertFormatsHandler","createRegenerateDocumentHandler","createRegenerateHandler","createRegenerateStatusHandler","encodeImageToThumbHash","decodeThumbHashToDataURL","imageOptimizer","pluginOptions","config","resolvedConfig","collections","collectionSlug","collection","find","c","slug","fields","push","disabled","hooks","beforeChange","afterChange","admin","components","beforeListTable","jobs","tasks","inputSchema","name","type","required","outputSchema","retries","handler","endpoints","path","method"],"mappings":"AAGA,SAASA,aAAa,QAAQ,gBAAe;AAC7C,SAASC,sBAAsB,QAAQ,kCAAiC;AACxE,SAASC,sBAAsB,QAAQ,0BAAyB;AAChE,SAASC,qBAAqB,QAAQ,yBAAwB;AAC9D,SAASC,2BAA2B,QAAQ,4BAA2B;AACvE,SAASC,+BAA+B,QAAQ,gCAA+B;AAC/E,SAASC,uBAAuB,EAAEC,6BAA6B,QAAQ,4BAA2B;AAIlG,SAASC,sBAAsB,EAAEC,wBAAwB,QAAQ,2BAA0B;AAE3F,OAAO,MAAMC,iBACX,CAACC,gBACD,CAACC;QACC,MAAMC,iBAAiBb,cAAcW;QAErC,IAAI,CAACC,OAAOE,WAAW,EAAE;YACvBF,OAAOE,WAAW,GAAG,EAAE;QACzB;QAEA,gEAAgE;QAChE,IAAK,MAAMC,kBAAkBF,eAAeC,WAAW,CAAE;YACvD,MAAME,aAAaJ,OAAOE,WAAW,CAACG,IAAI,CAAC,CAACC,IAAMA,EAAEC,IAAI,KAAKJ;YAE7D,IAAIC,YAAY;gBACdA,WAAWI,MAAM,CAACC,IAAI,CAACpB;YACzB;QACF;QAEA,uEAAuE;QACvE,IAAIY,eAAeS,QAAQ,EAAE;YAC3B,OAAOV;QACT;QAEA,gDAAgD;QAChD,IAAK,MAAMG,kBAAkBF,eAAeC,WAAW,CAAE;YACvD,MAAME,aAAaJ,OAAOE,WAAW,CAACG,IAAI,CAAC,CAACC,IAAMA,EAAEC,IAAI,KAAKJ;YAE7D,IAAIC,YAAY;gBACd,IAAI,CAACA,WAAWO,KAAK,EAAE;oBACrBP,WAAWO,KAAK,GAAG,CAAC;gBACtB;gBAEA,IAAI,CAACP,WAAWO,KAAK,CAACC,YAAY,EAAE;oBAClCR,WAAWO,KAAK,CAACC,YAAY,GAAG,EAAE;gBACpC;gBACAR,WAAWO,KAAK,CAACC,YAAY,CAACH,IAAI,CAACnB,uBAAuBW,gBAAgBE;gBAE1E,IAAI,CAACC,WAAWO,KAAK,CAACE,WAAW,EAAE;oBACjCT,WAAWO,KAAK,CAACE,WAAW,GAAG,EAAE;gBACnC;gBACAT,WAAWO,KAAK,CAACE,WAAW,CAACJ,IAAI,CAAClB,sBAAsBU,gBAAgBE;gBAExE,qDAAqD;gBACrD,IAAI,CAACC,WAAWU,KAAK,EAAE;oBACrBV,WAAWU,KAAK,GAAG,CAAC;gBACtB;gBACA,IAAI,CAACV,WAAWU,KAAK,CAACC,UAAU,EAAE;oBAChCX,WAAWU,KAAK,CAACC,UAAU,GAAG,CAAC;gBACjC;gBACA,IAAI,CAACX,WAAWU,KAAK,CAACC,UAAU,CAACC,eAAe,EAAE;oBAChDZ,WAAWU,KAAK,CAACC,UAAU,CAACC,eAAe,GAAG,EAAE;gBAClD;gBACAZ,WAAWU,KAAK,CAACC,UAAU,CAACC,eAAe,CAACP,IAAI,CAC9C;YAEJ;QACF;QAEA,4CAA4C;QAC5C,IAAI,CAACT,OAAOiB,IAAI,EAAE;YAChBjB,OAAOiB,IAAI,GAAG;gBAAEC,OAAO,EAAE;YAAC;QAC5B;QACA,IAAI,CAAClB,OAAOiB,IAAI,CAAEC,KAAK,EAAE;YACvBlB,OAAOiB,IAAI,CAAEC,KAAK,GAAG,EAAE;QACzB;QAEAlB,OAAOiB,IAAI,CAAEC,KAAK,CAAET,IAAI,CAAC;YACvBF,MAAM;YACNY,aAAa;gBACX;oBAAEC,MAAM;oBAAkBC,MAAM;oBAAQC,UAAU;gBAAK;gBACvD;oBAAEF,MAAM;oBAASC,MAAM;oBAAQC,UAAU;gBAAK;aAC/C;YACDC,cAAc;gBACZ;oBAAEH,MAAM;oBAAqBC,MAAM;gBAAS;aAC7C;YACDG,SAAS;YACTC,SAASjC,4BAA4BS;QACvC;QAEAD,OAAOiB,IAAI,CAAEC,KAAK,CAAET,IAAI,CAAC;YACvBF,MAAM;YACNY,aAAa;gBACX;oBAAEC,MAAM;oBAAkBC,MAAM;oBAAQC,UAAU;gBAAK;gBACvD;oBAAEF,MAAM;oBAASC,MAAM;oBAAQC,UAAU;gBAAK;aAC/C;YACDC,cAAc;gBACZ;oBAAEH,MAAM;oBAAUC,MAAM;gBAAO;gBAC/B;oBAAED,MAAM;oBAAUC,MAAM;gBAAO;aAChC;YACDG,SAAS;YACTC,SAAShC,gCAAgCQ;QAC3C;QAEA,kCAAkC;QAClC,IAAI,CAACD,OAAO0B,SAAS,EAAE1B,OAAO0B,SAAS,GAAG,EAAE;QAE5C1B,OAAO0B,SAAS,CAACjB,IAAI,CAAC;YACpBkB,MAAM;YACNC,QAAQ;YACRH,SAAS/B,wBAAwBO;QACnC;QAEAD,OAAO0B,SAAS,CAACjB,IAAI,CAAC;YACpBkB,MAAM;YACNC,QAAQ;YACRH,SAAS9B,8BAA8BM;QACzC;QAEA,OAAOD;IACT,EAAC"}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import type { Config } from 'payload'\nimport { deepMergeSimple } from 'payload/shared'\n\nimport type { ImageOptimizerConfig } from './types.js'\nimport { resolveConfig } from './defaults.js'\nimport { translations } from './translations/index.js'\nimport { getImageOptimizerField } from './fields/imageOptimizerField.js'\nimport { createBeforeChangeHook } from './hooks/beforeChange.js'\nimport { createAfterChangeHook } from './hooks/afterChange.js'\nimport { createConvertFormatsHandler } from './tasks/convertFormats.js'\nimport { createRegenerateDocumentHandler } from './tasks/regenerateDocument.js'\nimport { createRegenerateHandler, createRegenerateStatusHandler } from './endpoints/regenerate.js'\n\nexport type { ImageOptimizerConfig, ImageFormat, FormatQuality, CollectionOptimizerConfig, ImageOptimizerData, MediaResource, FieldsOverride } from './types.js'\nexport { defaultImageOptimizerFields } from './fields/imageOptimizerField.js'\n\nexport { encodeImageToThumbHash, decodeThumbHashToDataURL } from './utilities/thumbhash.js'\n\nexport const imageOptimizer =\n (pluginOptions: ImageOptimizerConfig) =>\n (config: Config): Config => {\n const resolvedConfig = resolveConfig(pluginOptions)\n const targetSlugs = Object.keys(resolvedConfig.collections)\n\n // Inject fields (and hooks when enabled) into targeted upload collections\n const collections = (config.collections || []).map((collection) => {\n if (!targetSlugs.includes(collection.slug)) {\n return collection\n }\n\n // Always inject fields for schema consistency (even when disabled)\n const fields = [...collection.fields, getImageOptimizerField(pluginOptions.fieldsOverride)]\n\n if (resolvedConfig.disabled) {\n return { ...collection, fields }\n }\n\n return {\n ...collection,\n fields,\n hooks: {\n ...collection.hooks,\n beforeChange: [\n ...(collection.hooks?.beforeChange || []),\n createBeforeChangeHook(resolvedConfig, collection.slug),\n ],\n afterChange: [\n ...(collection.hooks?.afterChange || []),\n createAfterChangeHook(resolvedConfig, collection.slug),\n ],\n },\n admin: {\n ...collection.admin,\n components: {\n ...collection.admin?.components,\n beforeListTable: [\n ...(collection.admin?.components?.beforeListTable || []),\n '@inoo-ch/payload-image-optimizer/client#RegenerationButton',\n ],\n },\n },\n }\n })\n\n const i18n = {\n ...config.i18n,\n translations: deepMergeSimple(translations, config.i18n?.translations ?? {}),\n }\n\n // If disabled, return with fields injected but no tasks/endpoints\n if (resolvedConfig.disabled) {\n return { ...config, collections, i18n }\n }\n\n return {\n ...config,\n collections,\n i18n,\n jobs: {\n ...config.jobs,\n tasks: [\n ...(config.jobs?.tasks || []),\n {\n slug: 'imageOptimizer_convertFormats',\n inputSchema: [\n { name: 'collectionSlug', type: 'text', required: true },\n { name: 'docId', type: 'text', required: true },\n ],\n outputSchema: [\n { name: 'variantsGenerated', type: 'number' },\n ],\n retries: 2,\n handler: createConvertFormatsHandler(resolvedConfig),\n } as any,\n {\n slug: 'imageOptimizer_regenerateDocument',\n inputSchema: [\n { name: 'collectionSlug', type: 'text', required: true },\n { name: 'docId', type: 'text', required: true },\n ],\n outputSchema: [\n { name: 'status', type: 'text' },\n { name: 'reason', type: 'text' },\n ],\n retries: 2,\n handler: createRegenerateDocumentHandler(resolvedConfig),\n } as any,\n ],\n },\n endpoints: [\n ...(config.endpoints ?? []),\n {\n path: '/image-optimizer/regenerate',\n method: 'post',\n handler: createRegenerateHandler(resolvedConfig),\n },\n {\n path: '/image-optimizer/regenerate',\n method: 'get',\n handler: createRegenerateStatusHandler(resolvedConfig),\n },\n ],\n }\n }\n"],"names":["deepMergeSimple","resolveConfig","translations","getImageOptimizerField","createBeforeChangeHook","createAfterChangeHook","createConvertFormatsHandler","createRegenerateDocumentHandler","createRegenerateHandler","createRegenerateStatusHandler","defaultImageOptimizerFields","encodeImageToThumbHash","decodeThumbHashToDataURL","imageOptimizer","pluginOptions","config","resolvedConfig","targetSlugs","Object","keys","collections","map","collection","includes","slug","fields","fieldsOverride","disabled","hooks","beforeChange","afterChange","admin","components","beforeListTable","i18n","jobs","tasks","inputSchema","name","type","required","outputSchema","retries","handler","endpoints","path","method"],"mappings":"AACA,SAASA,eAAe,QAAQ,iBAAgB;AAGhD,SAASC,aAAa,QAAQ,gBAAe;AAC7C,SAASC,YAAY,QAAQ,0BAAyB;AACtD,SAASC,sBAAsB,QAAQ,kCAAiC;AACxE,SAASC,sBAAsB,QAAQ,0BAAyB;AAChE,SAASC,qBAAqB,QAAQ,yBAAwB;AAC9D,SAASC,2BAA2B,QAAQ,4BAA2B;AACvE,SAASC,+BAA+B,QAAQ,gCAA+B;AAC/E,SAASC,uBAAuB,EAAEC,6BAA6B,QAAQ,4BAA2B;AAGlG,SAASC,2BAA2B,QAAQ,kCAAiC;AAE7E,SAASC,sBAAsB,EAAEC,wBAAwB,QAAQ,2BAA0B;AAE3F,OAAO,MAAMC,iBACX,CAACC,gBACD,CAACC;QACC,MAAMC,iBAAiBf,cAAca;QACrC,MAAMG,cAAcC,OAAOC,IAAI,CAACH,eAAeI,WAAW;QAE1D,0EAA0E;QAC1E,MAAMA,cAAc,AAACL,CAAAA,OAAOK,WAAW,IAAI,EAAE,AAAD,EAAGC,GAAG,CAAC,CAACC;YAClD,IAAI,CAACL,YAAYM,QAAQ,CAACD,WAAWE,IAAI,GAAG;gBAC1C,OAAOF;YACT;YAEA,mEAAmE;YACnE,MAAMG,SAAS;mBAAIH,WAAWG,MAAM;gBAAEtB,uBAAuBW,cAAcY,cAAc;aAAE;YAE3F,IAAIV,eAAeW,QAAQ,EAAE;gBAC3B,OAAO;oBAAE,GAAGL,UAAU;oBAAEG;gBAAO;YACjC;YAEA,OAAO;gBACL,GAAGH,UAAU;gBACbG;gBACAG,OAAO;oBACL,GAAGN,WAAWM,KAAK;oBACnBC,cAAc;2BACRP,WAAWM,KAAK,EAAEC,gBAAgB,EAAE;wBACxCzB,uBAAuBY,gBAAgBM,WAAWE,IAAI;qBACvD;oBACDM,aAAa;2BACPR,WAAWM,KAAK,EAAEE,eAAe,EAAE;wBACvCzB,sBAAsBW,gBAAgBM,WAAWE,IAAI;qBACtD;gBACH;gBACAO,OAAO;oBACL,GAAGT,WAAWS,KAAK;oBACnBC,YAAY;wBACV,GAAGV,WAAWS,KAAK,EAAEC,UAAU;wBAC/BC,iBAAiB;+BACXX,WAAWS,KAAK,EAAEC,YAAYC,mBAAmB,EAAE;4BACvD;yBACD;oBACH;gBACF;YACF;QACF;QAEA,MAAMC,OAAO;YACX,GAAGnB,OAAOmB,IAAI;YACdhC,cAAcF,gBAAgBE,cAAca,OAAOmB,IAAI,EAAEhC,gBAAgB,CAAC;QAC5E;QAEA,kEAAkE;QAClE,IAAIc,eAAeW,QAAQ,EAAE;YAC3B,OAAO;gBAAE,GAAGZ,MAAM;gBAAEK;gBAAac;YAAK;QACxC;QAEA,OAAO;YACL,GAAGnB,MAAM;YACTK;YACAc;YACAC,MAAM;gBACJ,GAAGpB,OAAOoB,IAAI;gBACdC,OAAO;uBACDrB,OAAOoB,IAAI,EAAEC,SAAS,EAAE;oBAC5B;wBACEZ,MAAM;wBACNa,aAAa;4BACX;gCAAEC,MAAM;gCAAkBC,MAAM;gCAAQC,UAAU;4BAAK;4BACvD;gCAAEF,MAAM;gCAASC,MAAM;gCAAQC,UAAU;4BAAK;yBAC/C;wBACDC,cAAc;4BACZ;gCAAEH,MAAM;gCAAqBC,MAAM;4BAAS;yBAC7C;wBACDG,SAAS;wBACTC,SAASrC,4BAA4BU;oBACvC;oBACA;wBACEQ,MAAM;wBACNa,aAAa;4BACX;gCAAEC,MAAM;gCAAkBC,MAAM;gCAAQC,UAAU;4BAAK;4BACvD;gCAAEF,MAAM;gCAASC,MAAM;gCAAQC,UAAU;4BAAK;yBAC/C;wBACDC,cAAc;4BACZ;gCAAEH,MAAM;gCAAUC,MAAM;4BAAO;4BAC/B;gCAAED,MAAM;gCAAUC,MAAM;4BAAO;yBAChC;wBACDG,SAAS;wBACTC,SAASpC,gCAAgCS;oBAC3C;iBACD;YACH;YACA4B,WAAW;mBACL7B,OAAO6B,SAAS,IAAI,EAAE;gBAC1B;oBACEC,MAAM;oBACNC,QAAQ;oBACRH,SAASnC,wBAAwBQ;gBACnC;gBACA;oBACE6B,MAAM;oBACNC,QAAQ;oBACRH,SAASlC,8BAA8BO;gBACzC;aACD;QACH;IACF,EAAC"}
@@ -2,6 +2,7 @@ import fs from 'fs/promises';
2
2
  import path from 'path';
3
3
  import { resolveCollectionConfig } from '../defaults.js';
4
4
  import { convertFormat } from '../processing/index.js';
5
+ import { resolveStaticDir } from '../utilities/resolveStaticDir.js';
5
6
  export const createConvertFormatsHandler = (resolvedConfig)=>{
6
7
  return async ({ input, req })=>{
7
8
  try {
@@ -10,13 +11,10 @@ export const createConvertFormatsHandler = (resolvedConfig)=>{
10
11
  id: input.docId
11
12
  });
12
13
  const collectionConfig = req.payload.collections[input.collectionSlug].config;
13
- let staticDir = typeof collectionConfig.upload === 'object' ? collectionConfig.upload.staticDir || '' : '';
14
+ const staticDir = resolveStaticDir(collectionConfig);
14
15
  if (!staticDir) {
15
16
  throw new Error(`No staticDir configured for collection "${input.collectionSlug}"`);
16
17
  }
17
- if (!path.isAbsolute(staticDir)) {
18
- staticDir = path.resolve(process.cwd(), staticDir);
19
- }
20
18
  // Sanitize filename to prevent path traversal
21
19
  const safeFilename = path.basename(doc.filename);
22
20
  const filePath = path.join(staticDir, safeFilename);
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/tasks/convertFormats.ts"],"sourcesContent":["import fs from 'fs/promises'\nimport path from 'path'\n\nimport type { CollectionSlug } from 'payload'\n\nimport type { ResolvedImageOptimizerConfig } from '../types.js'\nimport { resolveCollectionConfig } from '../defaults.js'\nimport { convertFormat } from '../processing/index.js'\n\nexport const createConvertFormatsHandler = (resolvedConfig: ResolvedImageOptimizerConfig) => {\n return async ({ input, req }: { input: { collectionSlug: string; docId: string }; req: any }) => {\n try {\n const doc = await req.payload.findByID({\n collection: input.collectionSlug as CollectionSlug,\n id: input.docId,\n })\n\n const collectionConfig = req.payload.collections[input.collectionSlug as keyof typeof req.payload.collections].config\n\n let staticDir: string =\n typeof collectionConfig.upload === 'object' ? collectionConfig.upload.staticDir || '' : ''\n if (!staticDir) {\n throw new Error(`No staticDir configured for collection \"${input.collectionSlug}\"`)\n }\n if (!path.isAbsolute(staticDir)) {\n staticDir = path.resolve(process.cwd(), staticDir)\n }\n\n // Sanitize filename to prevent path traversal\n const safeFilename = path.basename(doc.filename)\n const filePath = path.join(staticDir, safeFilename)\n const fileBuffer = await fs.readFile(filePath)\n\n const variants: Array<{\n filename: string\n filesize: number\n format: string\n height: number\n mimeType: string\n url: string\n width: number\n }> = []\n\n const perCollectionConfig = resolveCollectionConfig(resolvedConfig, input.collectionSlug)\n\n // When replaceOriginal is on, the main file is already in the primary format —\n // skip it and only generate variants for the remaining formats.\n const formatsToGenerate = perCollectionConfig.replaceOriginal && perCollectionConfig.formats.length > 0\n ? perCollectionConfig.formats.slice(1)\n : perCollectionConfig.formats\n\n for (const format of formatsToGenerate) {\n const result = await convertFormat(fileBuffer, format.format, format.quality)\n const variantFilename = `${path.parse(safeFilename).name}-optimized.${format.format}`\n\n await fs.writeFile(path.join(staticDir, variantFilename), result.buffer)\n\n variants.push({\n format: format.format,\n filename: variantFilename,\n filesize: result.size,\n width: result.width,\n height: result.height,\n mimeType: result.mimeType,\n url: `/api/${input.collectionSlug}/file/${variantFilename}`,\n })\n }\n\n await req.payload.update({\n collection: input.collectionSlug as CollectionSlug,\n id: input.docId,\n data: {\n imageOptimizer: {\n status: 'complete',\n variants,\n },\n },\n context: { imageOptimizer_skip: true },\n })\n\n return { output: { variantsGenerated: variants.length } }\n } catch (err) {\n const errorMessage = err instanceof Error ? err.message : String(err)\n\n try {\n await req.payload.update({\n collection: input.collectionSlug as CollectionSlug,\n id: input.docId,\n data: {\n imageOptimizer: {\n status: 'error',\n error: errorMessage,\n },\n },\n context: { imageOptimizer_skip: true },\n })\n } catch (updateErr) {\n req.payload.logger.error(\n { err: updateErr },\n 'Failed to persist error status for image optimizer',\n )\n }\n\n throw err\n }\n }\n}\n"],"names":["fs","path","resolveCollectionConfig","convertFormat","createConvertFormatsHandler","resolvedConfig","input","req","doc","payload","findByID","collection","collectionSlug","id","docId","collectionConfig","collections","config","staticDir","upload","Error","isAbsolute","resolve","process","cwd","safeFilename","basename","filename","filePath","join","fileBuffer","readFile","variants","perCollectionConfig","formatsToGenerate","replaceOriginal","formats","length","slice","format","result","quality","variantFilename","parse","name","writeFile","buffer","push","filesize","size","width","height","mimeType","url","update","data","imageOptimizer","status","context","imageOptimizer_skip","output","variantsGenerated","err","errorMessage","message","String","error","updateErr","logger"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/tasks/convertFormats.ts"],"sourcesContent":["import fs from 'fs/promises'\nimport path from 'path'\n\nimport type { CollectionSlug } from 'payload'\n\nimport type { ResolvedImageOptimizerConfig } from '../types.js'\nimport { resolveCollectionConfig } from '../defaults.js'\nimport { convertFormat } from '../processing/index.js'\nimport { resolveStaticDir } from '../utilities/resolveStaticDir.js'\n\nexport const createConvertFormatsHandler = (resolvedConfig: ResolvedImageOptimizerConfig) => {\n return async ({ input, req }: { input: { collectionSlug: string; docId: string }; req: any }) => {\n try {\n const doc = await req.payload.findByID({\n collection: input.collectionSlug as CollectionSlug,\n id: input.docId,\n })\n\n const collectionConfig = req.payload.collections[input.collectionSlug as keyof typeof req.payload.collections].config\n const staticDir = resolveStaticDir(collectionConfig)\n\n if (!staticDir) {\n throw new Error(`No staticDir configured for collection \"${input.collectionSlug}\"`)\n }\n\n // Sanitize filename to prevent path traversal\n const safeFilename = path.basename(doc.filename)\n const filePath = path.join(staticDir, safeFilename)\n const fileBuffer = await fs.readFile(filePath)\n\n const variants: Array<{\n filename: string\n filesize: number\n format: string\n height: number\n mimeType: string\n url: string\n width: number\n }> = []\n\n const perCollectionConfig = resolveCollectionConfig(resolvedConfig, input.collectionSlug)\n\n // When replaceOriginal is on, the main file is already in the primary format —\n // skip it and only generate variants for the remaining formats.\n const formatsToGenerate = perCollectionConfig.replaceOriginal && perCollectionConfig.formats.length > 0\n ? perCollectionConfig.formats.slice(1)\n : perCollectionConfig.formats\n\n for (const format of formatsToGenerate) {\n const result = await convertFormat(fileBuffer, format.format, format.quality)\n const variantFilename = `${path.parse(safeFilename).name}-optimized.${format.format}`\n\n await fs.writeFile(path.join(staticDir, variantFilename), result.buffer)\n\n variants.push({\n format: format.format,\n filename: variantFilename,\n filesize: result.size,\n width: result.width,\n height: result.height,\n mimeType: result.mimeType,\n url: `/api/${input.collectionSlug}/file/${variantFilename}`,\n })\n }\n\n await req.payload.update({\n collection: input.collectionSlug as CollectionSlug,\n id: input.docId,\n data: {\n imageOptimizer: {\n status: 'complete',\n variants,\n },\n },\n context: { imageOptimizer_skip: true },\n })\n\n return { output: { variantsGenerated: variants.length } }\n } catch (err) {\n const errorMessage = err instanceof Error ? err.message : String(err)\n\n try {\n await req.payload.update({\n collection: input.collectionSlug as CollectionSlug,\n id: input.docId,\n data: {\n imageOptimizer: {\n status: 'error',\n error: errorMessage,\n },\n },\n context: { imageOptimizer_skip: true },\n })\n } catch (updateErr) {\n req.payload.logger.error(\n { err: updateErr },\n 'Failed to persist error status for image optimizer',\n )\n }\n\n throw err\n }\n }\n}\n"],"names":["fs","path","resolveCollectionConfig","convertFormat","resolveStaticDir","createConvertFormatsHandler","resolvedConfig","input","req","doc","payload","findByID","collection","collectionSlug","id","docId","collectionConfig","collections","config","staticDir","Error","safeFilename","basename","filename","filePath","join","fileBuffer","readFile","variants","perCollectionConfig","formatsToGenerate","replaceOriginal","formats","length","slice","format","result","quality","variantFilename","parse","name","writeFile","buffer","push","filesize","size","width","height","mimeType","url","update","data","imageOptimizer","status","context","imageOptimizer_skip","output","variantsGenerated","err","errorMessage","message","String","error","updateErr","logger"],"mappings":"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"}
@@ -2,6 +2,7 @@ import fs from 'fs/promises';
2
2
  import path from 'path';
3
3
  import { resolveCollectionConfig } from '../defaults.js';
4
4
  import { stripAndResize, generateThumbHash, convertFormat } from '../processing/index.js';
5
+ import { resolveStaticDir } from '../utilities/resolveStaticDir.js';
5
6
  export const createRegenerateDocumentHandler = (resolvedConfig)=>{
6
7
  return async ({ input, req })=>{
7
8
  try {
@@ -19,13 +20,10 @@ export const createRegenerateDocumentHandler = (resolvedConfig)=>{
19
20
  };
20
21
  }
21
22
  const collectionConfig = req.payload.collections[input.collectionSlug].config;
22
- let staticDir = typeof collectionConfig.upload === 'object' ? collectionConfig.upload.staticDir || '' : '';
23
+ const staticDir = resolveStaticDir(collectionConfig);
23
24
  if (!staticDir) {
24
25
  throw new Error(`No staticDir configured for collection "${input.collectionSlug}"`);
25
26
  }
26
- if (!path.isAbsolute(staticDir)) {
27
- staticDir = path.resolve(process.cwd(), staticDir);
28
- }
29
27
  // Sanitize filename to prevent path traversal
30
28
  const safeFilename = path.basename(doc.filename);
31
29
  const filePath = path.join(staticDir, safeFilename);