@inoo-ch/payload-image-optimizer 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +226 -0
  3. package/dist/components/ImageBox.d.ts +22 -0
  4. package/dist/components/ImageBox.js +51 -0
  5. package/dist/components/ImageBox.js.map +1 -0
  6. package/dist/components/OptimizationStatus.d.ts +4 -0
  7. package/dist/components/OptimizationStatus.js +196 -0
  8. package/dist/components/OptimizationStatus.js.map +1 -0
  9. package/dist/components/RegenerationButton.d.ts +2 -0
  10. package/dist/components/RegenerationButton.js +212 -0
  11. package/dist/components/RegenerationButton.js.map +1 -0
  12. package/dist/defaults.d.ts +3 -0
  13. package/dist/defaults.js +35 -0
  14. package/dist/defaults.js.map +1 -0
  15. package/dist/endpoints/regenerate.d.ts +4 -0
  16. package/dist/endpoints/regenerate.js +144 -0
  17. package/dist/endpoints/regenerate.js.map +1 -0
  18. package/dist/exports/client.d.ts +6 -0
  19. package/dist/exports/client.js +6 -0
  20. package/dist/exports/client.js.map +1 -0
  21. package/dist/exports/rsc.d.ts +1 -0
  22. package/dist/exports/rsc.js +3 -0
  23. package/dist/exports/rsc.js.map +1 -0
  24. package/dist/fields/imageOptimizerField.d.ts +2 -0
  25. package/dist/fields/imageOptimizerField.js +75 -0
  26. package/dist/fields/imageOptimizerField.js.map +1 -0
  27. package/dist/hooks/afterChange.d.ts +3 -0
  28. package/dist/hooks/afterChange.js +40 -0
  29. package/dist/hooks/afterChange.js.map +1 -0
  30. package/dist/hooks/beforeChange.d.ts +3 -0
  31. package/dist/hooks/beforeChange.js +26 -0
  32. package/dist/hooks/beforeChange.js.map +1 -0
  33. package/dist/index.d.ts +5 -0
  34. package/dist/index.js +127 -0
  35. package/dist/index.js.map +1 -0
  36. package/dist/next-image.d.js +2 -0
  37. package/dist/next-image.d.js.map +1 -0
  38. package/dist/processing/index.d.ts +17 -0
  39. package/dist/processing/index.js +46 -0
  40. package/dist/processing/index.js.map +1 -0
  41. package/dist/tasks/convertFormats.d.ts +12 -0
  42. package/dist/tasks/convertFormats.js +84 -0
  43. package/dist/tasks/convertFormats.js.map +1 -0
  44. package/dist/tasks/regenerateDocument.d.ts +18 -0
  45. package/dist/tasks/regenerateDocument.js +121 -0
  46. package/dist/tasks/regenerateDocument.js.map +1 -0
  47. package/dist/types.d.ts +35 -0
  48. package/dist/types.js +3 -0
  49. package/dist/types.js.map +1 -0
  50. package/dist/utilities/getImageOptimizerProps.d.ts +32 -0
  51. package/dist/utilities/getImageOptimizerProps.js +55 -0
  52. package/dist/utilities/getImageOptimizerProps.js.map +1 -0
  53. package/dist/utilities/thumbhash.d.ts +2 -0
  54. package/dist/utilities/thumbhash.js +11 -0
  55. package/dist/utilities/thumbhash.js.map +1 -0
  56. package/package.json +141 -0
@@ -0,0 +1,212 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import React, { useState, useEffect, useCallback, useRef } from 'react';
4
+ export const RegenerationButton = ()=>{
5
+ const [isRunning, setIsRunning] = useState(false);
6
+ const [progress, setProgress] = useState(null);
7
+ const [queued, setQueued] = useState(null);
8
+ const [force, setForce] = useState(false);
9
+ const [error, setError] = useState(null);
10
+ const [collectionSlug, setCollectionSlug] = useState(null);
11
+ const intervalRef = useRef(null);
12
+ // Extract collection slug from URL after mount to avoid hydration mismatch
13
+ useEffect(()=>{
14
+ const slug = window.location.pathname.split('/collections/')[1]?.split('/')[0] ?? null;
15
+ setCollectionSlug(slug);
16
+ }, []);
17
+ const pollProgress = useCallback(async ()=>{
18
+ if (!collectionSlug) return;
19
+ try {
20
+ const res = await fetch(`/api/image-optimizer/regenerate?collection=${collectionSlug}`);
21
+ if (res.ok) {
22
+ const data = await res.json();
23
+ setProgress(data);
24
+ // Stop polling when no more pending
25
+ if (data.pending <= 0) {
26
+ setIsRunning(false);
27
+ if (intervalRef.current) {
28
+ clearInterval(intervalRef.current);
29
+ intervalRef.current = null;
30
+ }
31
+ }
32
+ }
33
+ } catch {
34
+ // ignore polling errors
35
+ }
36
+ }, [
37
+ collectionSlug
38
+ ]);
39
+ const handleRegenerate = async ()=>{
40
+ if (!collectionSlug) return;
41
+ setError(null);
42
+ setIsRunning(true);
43
+ setQueued(null);
44
+ setProgress(null);
45
+ try {
46
+ const res = await fetch('/api/image-optimizer/regenerate', {
47
+ method: 'POST',
48
+ headers: {
49
+ 'Content-Type': 'application/json'
50
+ },
51
+ body: JSON.stringify({
52
+ collectionSlug,
53
+ force
54
+ })
55
+ });
56
+ if (!res.ok) {
57
+ const data = await res.json();
58
+ throw new Error(data.error || 'Failed to start regeneration');
59
+ }
60
+ const data = await res.json();
61
+ setQueued(data.queued);
62
+ if (data.queued === 0) {
63
+ setIsRunning(false);
64
+ return;
65
+ }
66
+ // Start polling
67
+ intervalRef.current = setInterval(pollProgress, 2000);
68
+ } catch (err) {
69
+ setError(err instanceof Error ? err.message : String(err));
70
+ setIsRunning(false);
71
+ }
72
+ };
73
+ // Cleanup interval on unmount
74
+ useEffect(()=>{
75
+ return ()=>{
76
+ if (intervalRef.current) clearInterval(intervalRef.current);
77
+ };
78
+ }, []);
79
+ if (!collectionSlug) return null;
80
+ const progressPercent = progress && progress.total > 0 ? Math.round(progress.complete / progress.total * 100) : 0;
81
+ return /*#__PURE__*/ _jsxs("div", {
82
+ style: {
83
+ padding: '16px 24px',
84
+ borderBottom: '1px solid #e5e7eb',
85
+ display: 'flex',
86
+ alignItems: 'center',
87
+ gap: '16px',
88
+ flexWrap: 'wrap'
89
+ },
90
+ children: [
91
+ /*#__PURE__*/ _jsx("button", {
92
+ onClick: handleRegenerate,
93
+ disabled: isRunning,
94
+ style: {
95
+ backgroundColor: isRunning ? '#9ca3af' : '#4f46e5',
96
+ color: '#fff',
97
+ border: 'none',
98
+ borderRadius: '6px',
99
+ padding: '8px 16px',
100
+ fontSize: '14px',
101
+ fontWeight: 500,
102
+ cursor: isRunning ? 'not-allowed' : 'pointer'
103
+ },
104
+ children: isRunning ? 'Regenerating...' : 'Regenerate Images'
105
+ }),
106
+ /*#__PURE__*/ _jsxs("label", {
107
+ style: {
108
+ display: 'flex',
109
+ alignItems: 'center',
110
+ gap: '6px',
111
+ fontSize: '13px'
112
+ },
113
+ children: [
114
+ /*#__PURE__*/ _jsx("input", {
115
+ type: "checkbox",
116
+ checked: force,
117
+ onChange: (e)=>setForce(e.target.checked),
118
+ disabled: isRunning
119
+ }),
120
+ "Force re-process all"
121
+ ]
122
+ }),
123
+ error && /*#__PURE__*/ _jsx("span", {
124
+ style: {
125
+ color: '#ef4444',
126
+ fontSize: '13px'
127
+ },
128
+ children: error
129
+ }),
130
+ queued === 0 && !isRunning && /*#__PURE__*/ _jsx("span", {
131
+ style: {
132
+ color: '#10b981',
133
+ fontSize: '13px'
134
+ },
135
+ children: "All images already optimized."
136
+ }),
137
+ isRunning && progress && /*#__PURE__*/ _jsxs("div", {
138
+ style: {
139
+ flex: 1,
140
+ minWidth: '200px'
141
+ },
142
+ children: [
143
+ /*#__PURE__*/ _jsxs("div", {
144
+ style: {
145
+ display: 'flex',
146
+ justifyContent: 'space-between',
147
+ fontSize: '12px',
148
+ marginBottom: '4px'
149
+ },
150
+ children: [
151
+ /*#__PURE__*/ _jsxs("span", {
152
+ children: [
153
+ progress.complete,
154
+ " / ",
155
+ progress.total,
156
+ " complete"
157
+ ]
158
+ }),
159
+ progress.errored > 0 && /*#__PURE__*/ _jsxs("span", {
160
+ style: {
161
+ color: '#ef4444'
162
+ },
163
+ children: [
164
+ progress.errored,
165
+ " errors"
166
+ ]
167
+ }),
168
+ /*#__PURE__*/ _jsxs("span", {
169
+ children: [
170
+ progressPercent,
171
+ "%"
172
+ ]
173
+ })
174
+ ]
175
+ }),
176
+ /*#__PURE__*/ _jsx("div", {
177
+ style: {
178
+ height: '6px',
179
+ backgroundColor: '#e5e7eb',
180
+ borderRadius: '3px',
181
+ overflow: 'hidden'
182
+ },
183
+ children: /*#__PURE__*/ _jsx("div", {
184
+ style: {
185
+ height: '100%',
186
+ width: `${progressPercent}%`,
187
+ backgroundColor: '#10b981',
188
+ borderRadius: '3px',
189
+ transition: 'width 0.3s ease'
190
+ }
191
+ })
192
+ })
193
+ ]
194
+ }),
195
+ !isRunning && progress && progress.complete > 0 && queued !== 0 && /*#__PURE__*/ _jsxs("span", {
196
+ style: {
197
+ color: '#10b981',
198
+ fontSize: '13px'
199
+ },
200
+ children: [
201
+ "Done! ",
202
+ progress.complete,
203
+ "/",
204
+ progress.total,
205
+ " optimized."
206
+ ]
207
+ })
208
+ ]
209
+ });
210
+ };
211
+
212
+ //# sourceMappingURL=RegenerationButton.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/components/RegenerationButton.tsx"],"sourcesContent":["'use client'\n\nimport React, { useState, useEffect, useCallback, useRef } from 'react'\n\ntype RegenerationProgress = {\n total: number\n complete: number\n errored: number\n pending: number\n}\n\nexport const RegenerationButton: React.FC = () => {\n const [isRunning, setIsRunning] = useState(false)\n const [progress, setProgress] = useState<RegenerationProgress | null>(null)\n const [queued, setQueued] = useState<number | null>(null)\n const [force, setForce] = useState(false)\n const [error, setError] = useState<string | null>(null)\n const [collectionSlug, setCollectionSlug] = useState<string | null>(null)\n const intervalRef = useRef<ReturnType<typeof setInterval> | null>(null)\n\n // Extract collection slug from URL after mount to avoid hydration mismatch\n useEffect(() => {\n const slug = window.location.pathname.split('/collections/')[1]?.split('/')[0] ?? null\n setCollectionSlug(slug)\n }, [])\n\n const pollProgress = useCallback(async () => {\n if (!collectionSlug) return\n try {\n const res = await fetch(\n `/api/image-optimizer/regenerate?collection=${collectionSlug}`,\n )\n if (res.ok) {\n const data = await res.json()\n setProgress(data)\n // Stop polling when no more pending\n if (data.pending <= 0) {\n setIsRunning(false)\n if (intervalRef.current) {\n clearInterval(intervalRef.current)\n intervalRef.current = null\n }\n }\n }\n } catch {\n // ignore polling errors\n }\n }, [collectionSlug])\n\n const handleRegenerate = async () => {\n if (!collectionSlug) return\n setError(null)\n setIsRunning(true)\n setQueued(null)\n setProgress(null)\n\n try {\n const res = await fetch('/api/image-optimizer/regenerate', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ collectionSlug, force }),\n })\n\n if (!res.ok) {\n const data = await res.json()\n throw new Error(data.error || 'Failed to start regeneration')\n }\n\n const data = await res.json()\n setQueued(data.queued)\n\n if (data.queued === 0) {\n setIsRunning(false)\n return\n }\n\n // Start polling\n intervalRef.current = setInterval(pollProgress, 2000)\n } catch (err) {\n setError(err instanceof Error ? err.message : String(err))\n setIsRunning(false)\n }\n }\n\n // Cleanup interval on unmount\n useEffect(() => {\n return () => {\n if (intervalRef.current) clearInterval(intervalRef.current)\n }\n }, [])\n\n if (!collectionSlug) return null\n\n const progressPercent =\n progress && progress.total > 0\n ? Math.round((progress.complete / progress.total) * 100)\n : 0\n\n return (\n <div\n style={{\n padding: '16px 24px',\n borderBottom: '1px solid #e5e7eb',\n display: 'flex',\n alignItems: 'center',\n gap: '16px',\n flexWrap: 'wrap',\n }}\n >\n <button\n onClick={handleRegenerate}\n disabled={isRunning}\n style={{\n backgroundColor: isRunning ? '#9ca3af' : '#4f46e5',\n color: '#fff',\n border: 'none',\n borderRadius: '6px',\n padding: '8px 16px',\n fontSize: '14px',\n fontWeight: 500,\n cursor: isRunning ? 'not-allowed' : 'pointer',\n }}\n >\n {isRunning ? 'Regenerating...' : 'Regenerate Images'}\n </button>\n\n <label\n style={{ display: 'flex', alignItems: 'center', gap: '6px', fontSize: '13px' }}\n >\n <input\n type=\"checkbox\"\n checked={force}\n onChange={(e) => setForce(e.target.checked)}\n disabled={isRunning}\n />\n Force re-process all\n </label>\n\n {error && (\n <span style={{ color: '#ef4444', fontSize: '13px' }}>{error}</span>\n )}\n\n {queued === 0 && !isRunning && (\n <span style={{ color: '#10b981', fontSize: '13px' }}>\n All images already optimized.\n </span>\n )}\n\n {isRunning && progress && (\n <div style={{ flex: 1, minWidth: '200px' }}>\n <div\n style={{\n display: 'flex',\n justifyContent: 'space-between',\n fontSize: '12px',\n marginBottom: '4px',\n }}\n >\n <span>\n {progress.complete} / {progress.total} complete\n </span>\n {progress.errored > 0 && (\n <span style={{ color: '#ef4444' }}>{progress.errored} errors</span>\n )}\n <span>{progressPercent}%</span>\n </div>\n <div\n style={{\n height: '6px',\n backgroundColor: '#e5e7eb',\n borderRadius: '3px',\n overflow: 'hidden',\n }}\n >\n <div\n style={{\n height: '100%',\n width: `${progressPercent}%`,\n backgroundColor: '#10b981',\n borderRadius: '3px',\n transition: 'width 0.3s ease',\n }}\n />\n </div>\n </div>\n )}\n\n {!isRunning && progress && progress.complete > 0 && queued !== 0 && (\n <span style={{ color: '#10b981', fontSize: '13px' }}>\n Done! {progress.complete}/{progress.total} optimized.\n </span>\n )}\n </div>\n )\n}\n"],"names":["React","useState","useEffect","useCallback","useRef","RegenerationButton","isRunning","setIsRunning","progress","setProgress","queued","setQueued","force","setForce","error","setError","collectionSlug","setCollectionSlug","intervalRef","slug","window","location","pathname","split","pollProgress","res","fetch","ok","data","json","pending","current","clearInterval","handleRegenerate","method","headers","body","JSON","stringify","Error","setInterval","err","message","String","progressPercent","total","Math","round","complete","div","style","padding","borderBottom","display","alignItems","gap","flexWrap","button","onClick","disabled","backgroundColor","color","border","borderRadius","fontSize","fontWeight","cursor","label","input","type","checked","onChange","e","target","span","flex","minWidth","justifyContent","marginBottom","errored","height","overflow","width","transition"],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { ImageOptimizerConfig, ResolvedCollectionOptimizerConfig, ResolvedImageOptimizerConfig } from './types.js';
2
+ export declare const resolveConfig: (config: ImageOptimizerConfig) => ResolvedImageOptimizerConfig;
3
+ export declare const resolveCollectionConfig: (resolvedConfig: ResolvedImageOptimizerConfig, collectionSlug: string) => ResolvedCollectionOptimizerConfig;
@@ -0,0 +1,35 @@
1
+ export const resolveConfig = (config)=>({
2
+ collections: config.collections,
3
+ disabled: config.disabled ?? false,
4
+ formats: config.formats ?? [
5
+ {
6
+ format: 'webp',
7
+ quality: 80
8
+ },
9
+ {
10
+ format: 'avif',
11
+ quality: 65
12
+ }
13
+ ],
14
+ generateThumbHash: config.generateThumbHash ?? true,
15
+ maxDimensions: config.maxDimensions ?? {
16
+ width: 2560,
17
+ height: 2560
18
+ },
19
+ stripMetadata: config.stripMetadata ?? true
20
+ });
21
+ export const resolveCollectionConfig = (resolvedConfig, collectionSlug)=>{
22
+ const collectionValue = resolvedConfig.collections[collectionSlug];
23
+ if (!collectionValue || collectionValue === true) {
24
+ return {
25
+ formats: resolvedConfig.formats,
26
+ maxDimensions: resolvedConfig.maxDimensions
27
+ };
28
+ }
29
+ return {
30
+ formats: collectionValue.formats ?? resolvedConfig.formats,
31
+ maxDimensions: collectionValue.maxDimensions ?? resolvedConfig.maxDimensions
32
+ };
33
+ };
34
+
35
+ //# sourceMappingURL=defaults.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/defaults.ts"],"sourcesContent":["import type { CollectionSlug } from 'payload'\n\nimport type { ImageOptimizerConfig, ResolvedCollectionOptimizerConfig, ResolvedImageOptimizerConfig } from './types.js'\n\nexport const resolveConfig = (config: ImageOptimizerConfig): ResolvedImageOptimizerConfig => ({\n collections: config.collections,\n disabled: config.disabled ?? false,\n formats: config.formats ?? [\n { format: 'webp', quality: 80 },\n { format: 'avif', quality: 65 },\n ],\n generateThumbHash: config.generateThumbHash ?? true,\n maxDimensions: config.maxDimensions ?? { width: 2560, height: 2560 },\n stripMetadata: config.stripMetadata ?? true,\n})\n\nexport const resolveCollectionConfig = (\n resolvedConfig: ResolvedImageOptimizerConfig,\n collectionSlug: string,\n): ResolvedCollectionOptimizerConfig => {\n const collectionValue = resolvedConfig.collections[collectionSlug as CollectionSlug]\n\n if (!collectionValue || collectionValue === true) {\n return {\n formats: resolvedConfig.formats,\n maxDimensions: resolvedConfig.maxDimensions,\n }\n }\n\n return {\n formats: collectionValue.formats ?? resolvedConfig.formats,\n maxDimensions: collectionValue.maxDimensions ?? resolvedConfig.maxDimensions,\n }\n}\n"],"names":["resolveConfig","config","collections","disabled","formats","format","quality","generateThumbHash","maxDimensions","width","height","stripMetadata","resolveCollectionConfig","resolvedConfig","collectionSlug","collectionValue"],"mappings":"AAIA,OAAO,MAAMA,gBAAgB,CAACC,SAAgE,CAAA;QAC5FC,aAAaD,OAAOC,WAAW;QAC/BC,UAAUF,OAAOE,QAAQ,IAAI;QAC7BC,SAASH,OAAOG,OAAO,IAAI;YACzB;gBAAEC,QAAQ;gBAAQC,SAAS;YAAG;YAC9B;gBAAED,QAAQ;gBAAQC,SAAS;YAAG;SAC/B;QACDC,mBAAmBN,OAAOM,iBAAiB,IAAI;QAC/CC,eAAeP,OAAOO,aAAa,IAAI;YAAEC,OAAO;YAAMC,QAAQ;QAAK;QACnEC,eAAeV,OAAOU,aAAa,IAAI;IACzC,CAAA,EAAE;AAEF,OAAO,MAAMC,0BAA0B,CACrCC,gBACAC;IAEA,MAAMC,kBAAkBF,eAAeX,WAAW,CAACY,eAAiC;IAEpF,IAAI,CAACC,mBAAmBA,oBAAoB,MAAM;QAChD,OAAO;YACLX,SAASS,eAAeT,OAAO;YAC/BI,eAAeK,eAAeL,aAAa;QAC7C;IACF;IAEA,OAAO;QACLJ,SAASW,gBAAgBX,OAAO,IAAIS,eAAeT,OAAO;QAC1DI,eAAeO,gBAAgBP,aAAa,IAAIK,eAAeL,aAAa;IAC9E;AACF,EAAC"}
@@ -0,0 +1,4 @@
1
+ import type { PayloadHandler } from 'payload';
2
+ import type { ResolvedImageOptimizerConfig } from '../types.js';
3
+ export declare const createRegenerateHandler: (resolvedConfig: ResolvedImageOptimizerConfig) => PayloadHandler;
4
+ export declare const createRegenerateStatusHandler: (resolvedConfig: ResolvedImageOptimizerConfig) => PayloadHandler;
@@ -0,0 +1,144 @@
1
+ export const createRegenerateHandler = (resolvedConfig)=>{
2
+ const handler = async (req)=>{
3
+ if (!req.user) {
4
+ return Response.json({
5
+ error: 'Unauthorized'
6
+ }, {
7
+ status: 401
8
+ });
9
+ }
10
+ let body;
11
+ try {
12
+ body = await req.json();
13
+ } catch {
14
+ body = {};
15
+ }
16
+ const collectionSlug = body.collectionSlug;
17
+ if (!collectionSlug || !resolvedConfig.collections[collectionSlug]) {
18
+ return Response.json({
19
+ error: 'Invalid or unconfigured collection slug'
20
+ }, {
21
+ status: 400
22
+ });
23
+ }
24
+ // Find all image documents in the collection
25
+ const where = {
26
+ mimeType: {
27
+ contains: 'image/'
28
+ }
29
+ };
30
+ // Unless force=true, skip already-processed docs
31
+ if (!body.force) {
32
+ where.or = [
33
+ {
34
+ 'imageOptimizer.status': {
35
+ not_equals: 'complete'
36
+ }
37
+ },
38
+ {
39
+ 'imageOptimizer.status': {
40
+ exists: false
41
+ }
42
+ }
43
+ ];
44
+ }
45
+ let queued = 0;
46
+ let page = 1;
47
+ let hasMore = true;
48
+ while(hasMore){
49
+ const result = await req.payload.find({
50
+ collection: collectionSlug,
51
+ limit: 50,
52
+ page,
53
+ depth: 0,
54
+ where,
55
+ sort: 'createdAt'
56
+ });
57
+ for (const doc of result.docs){
58
+ await req.payload.jobs.queue({
59
+ task: 'imageOptimizer_regenerateDocument',
60
+ input: {
61
+ collectionSlug,
62
+ docId: String(doc.id)
63
+ }
64
+ });
65
+ queued++;
66
+ }
67
+ hasMore = result.hasNextPage;
68
+ page++;
69
+ }
70
+ // Fire the job runner (non-blocking)
71
+ if (queued > 0) {
72
+ req.payload.jobs.run().catch((err)=>{
73
+ req.payload.logger.error({
74
+ err
75
+ }, 'Regeneration job runner failed');
76
+ });
77
+ }
78
+ return Response.json({
79
+ queued,
80
+ collectionSlug
81
+ });
82
+ };
83
+ return handler;
84
+ };
85
+ export const createRegenerateStatusHandler = (resolvedConfig)=>{
86
+ const handler = async (req)=>{
87
+ if (!req.user) {
88
+ return Response.json({
89
+ error: 'Unauthorized'
90
+ }, {
91
+ status: 401
92
+ });
93
+ }
94
+ const url = new URL(req.url);
95
+ const collectionSlug = url.searchParams.get('collection');
96
+ if (!collectionSlug || !resolvedConfig.collections[collectionSlug]) {
97
+ return Response.json({
98
+ error: 'Invalid collection slug'
99
+ }, {
100
+ status: 400
101
+ });
102
+ }
103
+ const total = await req.payload.count({
104
+ collection: collectionSlug,
105
+ where: {
106
+ mimeType: {
107
+ contains: 'image/'
108
+ }
109
+ }
110
+ });
111
+ const complete = await req.payload.count({
112
+ collection: collectionSlug,
113
+ where: {
114
+ mimeType: {
115
+ contains: 'image/'
116
+ },
117
+ 'imageOptimizer.status': {
118
+ equals: 'complete'
119
+ }
120
+ }
121
+ });
122
+ const errored = await req.payload.count({
123
+ collection: collectionSlug,
124
+ where: {
125
+ mimeType: {
126
+ contains: 'image/'
127
+ },
128
+ 'imageOptimizer.status': {
129
+ equals: 'error'
130
+ }
131
+ }
132
+ });
133
+ return Response.json({
134
+ collectionSlug,
135
+ total: total.totalDocs,
136
+ complete: complete.totalDocs,
137
+ errored: errored.totalDocs,
138
+ pending: total.totalDocs - complete.totalDocs - errored.totalDocs
139
+ });
140
+ };
141
+ return handler;
142
+ };
143
+
144
+ //# sourceMappingURL=regenerate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/endpoints/regenerate.ts"],"sourcesContent":["import type { PayloadHandler } from 'payload'\nimport type { CollectionSlug } from 'payload'\n\nimport type { ResolvedImageOptimizerConfig } from '../types.js'\n\nexport const createRegenerateHandler = (resolvedConfig: ResolvedImageOptimizerConfig) => {\n const handler: PayloadHandler = async (req) => {\n if (!req.user) {\n return Response.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n let body: { collectionSlug?: string; force?: boolean }\n try {\n body = await req.json!()\n } catch {\n body = {}\n }\n\n const collectionSlug = body.collectionSlug\n if (!collectionSlug || !resolvedConfig.collections[collectionSlug as CollectionSlug]) {\n return Response.json(\n { error: 'Invalid or unconfigured collection slug' },\n { status: 400 },\n )\n }\n\n // Find all image documents in the collection\n const where: any = {\n mimeType: { contains: 'image/' },\n }\n // Unless force=true, skip already-processed docs\n if (!body.force) {\n where.or = [\n { 'imageOptimizer.status': { not_equals: 'complete' } },\n { 'imageOptimizer.status': { exists: false } },\n ]\n }\n\n let queued = 0\n let page = 1\n let hasMore = true\n\n while (hasMore) {\n const result = await req.payload.find({\n collection: collectionSlug as CollectionSlug,\n limit: 50,\n page,\n depth: 0,\n where,\n sort: 'createdAt',\n })\n\n for (const doc of result.docs) {\n await req.payload.jobs.queue({\n task: 'imageOptimizer_regenerateDocument',\n input: {\n collectionSlug,\n docId: String(doc.id),\n },\n })\n queued++\n }\n\n hasMore = result.hasNextPage\n page++\n }\n\n // Fire the job runner (non-blocking)\n if (queued > 0) {\n req.payload.jobs.run().catch((err: unknown) => {\n req.payload.logger.error({ err }, 'Regeneration job runner failed')\n })\n }\n\n return Response.json({ queued, collectionSlug })\n }\n\n return handler\n}\n\nexport const createRegenerateStatusHandler = (resolvedConfig: ResolvedImageOptimizerConfig) => {\n const handler: PayloadHandler = async (req) => {\n if (!req.user) {\n return Response.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n const url = new URL(req.url!)\n const collectionSlug = url.searchParams.get('collection')\n\n if (!collectionSlug || !resolvedConfig.collections[collectionSlug as CollectionSlug]) {\n return Response.json({ error: 'Invalid collection slug' }, { status: 400 })\n }\n\n const total = await req.payload.count({\n collection: collectionSlug as CollectionSlug,\n where: { mimeType: { contains: 'image/' } },\n })\n\n const complete = await req.payload.count({\n collection: collectionSlug as CollectionSlug,\n where: {\n mimeType: { contains: 'image/' },\n 'imageOptimizer.status': { equals: 'complete' },\n },\n })\n\n const errored = await req.payload.count({\n collection: collectionSlug as CollectionSlug,\n where: {\n mimeType: { contains: 'image/' },\n 'imageOptimizer.status': { equals: 'error' },\n },\n })\n\n return Response.json({\n collectionSlug,\n total: total.totalDocs,\n complete: complete.totalDocs,\n errored: errored.totalDocs,\n pending: total.totalDocs - complete.totalDocs - errored.totalDocs,\n })\n }\n\n return handler\n}\n"],"names":["createRegenerateHandler","resolvedConfig","handler","req","user","Response","json","error","status","body","collectionSlug","collections","where","mimeType","contains","force","or","not_equals","exists","queued","page","hasMore","result","payload","find","collection","limit","depth","sort","doc","docs","jobs","queue","task","input","docId","String","id","hasNextPage","run","catch","err","logger","createRegenerateStatusHandler","url","URL","searchParams","get","total","count","complete","equals","errored","totalDocs","pending"],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ export { OptimizationStatus } from '../components/OptimizationStatus.js';
2
+ export { ImageBox } from '../components/ImageBox.js';
3
+ export type { ImageBoxProps } from '../components/ImageBox.js';
4
+ export { getImageOptimizerProps } from '../utilities/getImageOptimizerProps.js';
5
+ export type { ImageOptimizerProps } from '../utilities/getImageOptimizerProps.js';
6
+ export { RegenerationButton } from '../components/RegenerationButton.js';
@@ -0,0 +1,6 @@
1
+ export { OptimizationStatus } from '../components/OptimizationStatus.js';
2
+ export { ImageBox } from '../components/ImageBox.js';
3
+ export { getImageOptimizerProps } from '../utilities/getImageOptimizerProps.js';
4
+ export { RegenerationButton } from '../components/RegenerationButton.js';
5
+
6
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/exports/client.ts"],"sourcesContent":["export { OptimizationStatus } from '../components/OptimizationStatus.js'\nexport { ImageBox } from '../components/ImageBox.js'\nexport type { ImageBoxProps } from '../components/ImageBox.js'\nexport { getImageOptimizerProps } from '../utilities/getImageOptimizerProps.js'\nexport type { ImageOptimizerProps } from '../utilities/getImageOptimizerProps.js'\nexport { RegenerationButton } from '../components/RegenerationButton.js'\n"],"names":["OptimizationStatus","ImageBox","getImageOptimizerProps","RegenerationButton"],"mappings":"AAAA,SAASA,kBAAkB,QAAQ,sCAAqC;AACxE,SAASC,QAAQ,QAAQ,4BAA2B;AAEpD,SAASC,sBAAsB,QAAQ,yCAAwC;AAE/E,SAASC,kBAAkB,QAAQ,sCAAqC"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,3 @@
1
+ // No RSC components exported by this plugin
2
+
3
+ //# sourceMappingURL=rsc.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/exports/rsc.ts"],"sourcesContent":["// No RSC components exported by this plugin\n"],"names":[],"mappings":"AAAA,4CAA4C"}
@@ -0,0 +1,2 @@
1
+ import type { GroupField } from 'payload';
2
+ export declare const getImageOptimizerField: () => GroupField;
@@ -0,0 +1,75 @@
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
+ },
11
+ fields: [
12
+ {
13
+ name: 'thumbHash',
14
+ type: 'text'
15
+ },
16
+ {
17
+ name: 'originalSize',
18
+ type: 'number'
19
+ },
20
+ {
21
+ name: 'optimizedSize',
22
+ type: 'number'
23
+ },
24
+ {
25
+ name: 'status',
26
+ type: 'select',
27
+ options: [
28
+ 'pending',
29
+ 'processing',
30
+ 'complete',
31
+ 'error'
32
+ ]
33
+ },
34
+ {
35
+ name: 'error',
36
+ type: 'text'
37
+ },
38
+ {
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
+ ]
71
+ }
72
+ ]
73
+ });
74
+
75
+ //# sourceMappingURL=imageOptimizerField.js.map
@@ -0,0 +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"}
@@ -0,0 +1,3 @@
1
+ import type { CollectionAfterChangeHook } from 'payload';
2
+ import type { ResolvedImageOptimizerConfig } from '../types.js';
3
+ export declare const createAfterChangeHook: (resolvedConfig: ResolvedImageOptimizerConfig, collectionSlug: string) => CollectionAfterChangeHook;
@@ -0,0 +1,40 @@
1
+ import fs from 'fs/promises';
2
+ import path from 'path';
3
+ export const createAfterChangeHook = (resolvedConfig, collectionSlug)=>{
4
+ return async ({ context, doc, req })=>{
5
+ if (context?.imageOptimizer_skip) return doc;
6
+ if (!req.file || !req.file.data || !req.file.mimetype?.startsWith('image/')) return doc;
7
+ // Overwrite the file on disk with the processed (stripped/resized) buffer
8
+ // Payload 3.0 writes the original buffer to disk; we replace it here
9
+ const processedBuffer = context.imageOptimizer_processedBuffer;
10
+ if (processedBuffer && doc.filename) {
11
+ const collectionConfig = req.payload.collections[collectionSlug].config;
12
+ let staticDir = typeof collectionConfig.upload === 'object' ? collectionConfig.upload.staticDir || '' : '';
13
+ if (staticDir && !path.isAbsolute(staticDir)) {
14
+ staticDir = path.resolve(process.cwd(), staticDir);
15
+ }
16
+ if (staticDir) {
17
+ // Sanitize filename to prevent path traversal
18
+ const safeFilename = path.basename(doc.filename);
19
+ const filePath = path.join(staticDir, safeFilename);
20
+ await fs.writeFile(filePath, processedBuffer);
21
+ }
22
+ }
23
+ // Queue async format conversion job
24
+ await req.payload.jobs.queue({
25
+ task: 'imageOptimizer_convertFormats',
26
+ input: {
27
+ collectionSlug,
28
+ docId: String(doc.id)
29
+ }
30
+ });
31
+ req.payload.jobs.run().catch((err)=>{
32
+ req.payload.logger.error({
33
+ err
34
+ }, 'Image optimizer job runner failed');
35
+ });
36
+ return doc;
37
+ };
38
+ };
39
+
40
+ //# sourceMappingURL=afterChange.js.map
@@ -0,0 +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'\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 // Overwrite the file on disk with the processed (stripped/resized) 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) {\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 if (staticDir) {\n // Sanitize filename to prevent path traversal\n const safeFilename = path.basename(doc.filename as string)\n const filePath = path.join(staticDir, safeFilename)\n await fs.writeFile(filePath, processedBuffer)\n }\n }\n\n // Queue async format conversion job\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","createAfterChangeHook","resolvedConfig","collectionSlug","context","doc","req","imageOptimizer_skip","file","data","mimetype","startsWith","processedBuffer","imageOptimizer_processedBuffer","filename","collectionConfig","payload","collections","config","staticDir","upload","isAbsolute","resolve","process","cwd","safeFilename","basename","filePath","join","writeFile","jobs","queue","task","input","docId","String","id","run","catch","err","logger","error"],"mappings":"AAAA,OAAOA,QAAQ,cAAa;AAC5B,OAAOC,UAAU,OAAM;AAKvB,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,0EAA0E;QAC1E,qEAAqE;QACrE,MAAMO,kBAAkBR,QAAQS,8BAA8B;QAC9D,IAAID,mBAAmBP,IAAIS,QAAQ,EAAE;YACnC,MAAMC,mBAAmBT,IAAIU,OAAO,CAACC,WAAW,CAACd,eAAuD,CAACe,MAAM;YAC/G,IAAIC,YACF,OAAOJ,iBAAiBK,MAAM,KAAK,WAAWL,iBAAiBK,MAAM,CAACD,SAAS,IAAI,KAAK;YAE1F,IAAIA,aAAa,CAACnB,KAAKqB,UAAU,CAACF,YAAY;gBAC5CA,YAAYnB,KAAKsB,OAAO,CAACC,QAAQC,GAAG,IAAIL;YAC1C;YAEA,IAAIA,WAAW;gBACb,8CAA8C;gBAC9C,MAAMM,eAAezB,KAAK0B,QAAQ,CAACrB,IAAIS,QAAQ;gBAC/C,MAAMa,WAAW3B,KAAK4B,IAAI,CAACT,WAAWM;gBACtC,MAAM1B,GAAG8B,SAAS,CAACF,UAAUf;YAC/B;QACF;QAEA,oCAAoC;QACpC,MAAMN,IAAIU,OAAO,CAACc,IAAI,CAACC,KAAK,CAAC;YAC3BC,MAAM;YACNC,OAAO;gBACL9B;gBACA+B,OAAOC,OAAO9B,IAAI+B,EAAE;YACtB;QACF;QAEA9B,IAAIU,OAAO,CAACc,IAAI,CAACO,GAAG,GAAGC,KAAK,CAAC,CAACC;YAC5BjC,IAAIU,OAAO,CAACwB,MAAM,CAACC,KAAK,CAAC;gBAAEF;YAAI,GAAG;QACpC;QAEA,OAAOlC;IACT;AACF,EAAC"}
@@ -0,0 +1,3 @@
1
+ import type { CollectionBeforeChangeHook } from 'payload';
2
+ import type { ResolvedImageOptimizerConfig } from '../types.js';
3
+ export declare const createBeforeChangeHook: (resolvedConfig: ResolvedImageOptimizerConfig, collectionSlug: string) => CollectionBeforeChangeHook;