@caprionlinesrl/puck-plugin-media 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +422 -0
- package/dist/index.css +2288 -0
- package/dist/index.css.map +1 -0
- package/dist/index.d.mts +479 -0
- package/dist/index.d.ts +479 -0
- package/dist/index.js +3029 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +2990 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +69 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Capri On Line S.r.l.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,422 @@
|
|
|
1
|
+
# @caprionlinesrl/puck-plugin-media
|
|
2
|
+
|
|
3
|
+
A Puck plugin for media management with support for images, galleries, and documents.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Image Field** - Select images with upload, multilingual alt text, and bulk delete
|
|
8
|
+
- **Gallery Field** - Create and manage image galleries with drag & drop upload
|
|
9
|
+
- **Document Field** - Upload and manage documents with multilingual titles
|
|
10
|
+
- **Media Panel** - Browse and manage all media directly in Puck's sidebar
|
|
11
|
+
- **Upload Support** - Drag & drop with progress tracking and file validation
|
|
12
|
+
- **Multilingual** - Alt text and titles in multiple languages
|
|
13
|
+
- **Search & Pagination** - Filter and load more items
|
|
14
|
+
- **Manage Mode** - Bulk select and delete items
|
|
15
|
+
- **Fully Typed** - Complete TypeScript support
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install @caprionlinesrl/puck-plugin-media
|
|
21
|
+
# or
|
|
22
|
+
yarn add @caprionlinesrl/puck-plugin-media
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Quick Start
|
|
26
|
+
|
|
27
|
+
### 1. Create the plugin
|
|
28
|
+
|
|
29
|
+
```tsx
|
|
30
|
+
import { createMediaPlugin } from '@caprionlinesrl/puck-plugin-media';
|
|
31
|
+
|
|
32
|
+
const mediaPlugin = createMediaPlugin({
|
|
33
|
+
languages: [
|
|
34
|
+
{ code: 'en', label: 'English' },
|
|
35
|
+
{ code: 'it', label: 'Italiano' },
|
|
36
|
+
],
|
|
37
|
+
|
|
38
|
+
image: {
|
|
39
|
+
fetchList: async ({ query, page, pageSize }) => {
|
|
40
|
+
const res = await fetch(`/api/images?q=${query || ''}&page=${page}&limit=${pageSize}`);
|
|
41
|
+
const data = await res.json();
|
|
42
|
+
return { items: data.items, hasMore: data.hasMore };
|
|
43
|
+
},
|
|
44
|
+
upload: async (file, { onProgress }) => {
|
|
45
|
+
// Upload implementation with progress callback
|
|
46
|
+
const formData = new FormData();
|
|
47
|
+
formData.append('file', file);
|
|
48
|
+
const res = await fetch('/api/images/upload', { method: 'POST', body: formData });
|
|
49
|
+
return res.json();
|
|
50
|
+
},
|
|
51
|
+
update: async (id, data) => {
|
|
52
|
+
const res = await fetch(`/api/images/${id}`, {
|
|
53
|
+
method: 'PATCH',
|
|
54
|
+
body: JSON.stringify(data),
|
|
55
|
+
});
|
|
56
|
+
return res.json();
|
|
57
|
+
},
|
|
58
|
+
delete: async (id) => {
|
|
59
|
+
await fetch(`/api/images/${id}`, { method: 'DELETE' });
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
|
|
63
|
+
// Optional: Gallery support
|
|
64
|
+
gallery: {
|
|
65
|
+
fetchList: async (params) => { /* ... */ },
|
|
66
|
+
fetch: async (id) => { /* ... */ },
|
|
67
|
+
create: async (name) => { /* ... */ },
|
|
68
|
+
delete: async (id) => { /* ... */ },
|
|
69
|
+
upload: async (galleryId, file, callbacks) => { /* ... */ },
|
|
70
|
+
removeImage: async (galleryId, imageId) => { /* ... */ },
|
|
71
|
+
updateImage: async (galleryId, imageId, data) => { /* ... */ },
|
|
72
|
+
},
|
|
73
|
+
|
|
74
|
+
// Optional: Document support
|
|
75
|
+
document: {
|
|
76
|
+
fetchList: async (params) => { /* ... */ },
|
|
77
|
+
upload: async (file, callbacks) => { /* ... */ },
|
|
78
|
+
update: async (id, data) => { /* ... */ },
|
|
79
|
+
delete: async (id) => { /* ... */ },
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### 2. Add to Puck
|
|
85
|
+
|
|
86
|
+
```tsx
|
|
87
|
+
import { Puck } from '@puckeditor/core';
|
|
88
|
+
import '@caprionlinesrl/puck-plugin-media/styles.css';
|
|
89
|
+
|
|
90
|
+
<Puck
|
|
91
|
+
config={config}
|
|
92
|
+
data={pageData}
|
|
93
|
+
plugins={[mediaPlugin]}
|
|
94
|
+
/>
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### 3. Use fields in your blocks
|
|
98
|
+
|
|
99
|
+
```tsx
|
|
100
|
+
const config = {
|
|
101
|
+
components: {
|
|
102
|
+
Hero: {
|
|
103
|
+
fields: {
|
|
104
|
+
backgroundImage: {
|
|
105
|
+
type: 'image',
|
|
106
|
+
label: 'Background Image',
|
|
107
|
+
},
|
|
108
|
+
title: {
|
|
109
|
+
type: 'text',
|
|
110
|
+
label: 'Title',
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
render: ({ backgroundImage, title }) => (
|
|
114
|
+
<div style={{ backgroundImage: `url(${backgroundImage?.url})` }}>
|
|
115
|
+
<h1>{title}</h1>
|
|
116
|
+
</div>
|
|
117
|
+
),
|
|
118
|
+
},
|
|
119
|
+
|
|
120
|
+
PhotoGallery: {
|
|
121
|
+
fields: {
|
|
122
|
+
gallery: {
|
|
123
|
+
type: 'gallery',
|
|
124
|
+
label: 'Photo Gallery',
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
render: ({ gallery }) => (
|
|
128
|
+
<div className="gallery">
|
|
129
|
+
{gallery?.images.map((img) => (
|
|
130
|
+
<img key={img.id} src={img.url} alt={img.alt?.en || ''} />
|
|
131
|
+
))}
|
|
132
|
+
</div>
|
|
133
|
+
),
|
|
134
|
+
},
|
|
135
|
+
|
|
136
|
+
Download: {
|
|
137
|
+
fields: {
|
|
138
|
+
document: {
|
|
139
|
+
type: 'document',
|
|
140
|
+
label: 'Download File',
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
render: ({ document }) => (
|
|
144
|
+
<a href={document?.url} download>
|
|
145
|
+
{document?.title?.en || document?.filename}
|
|
146
|
+
</a>
|
|
147
|
+
),
|
|
148
|
+
},
|
|
149
|
+
},
|
|
150
|
+
};
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## Configuration
|
|
154
|
+
|
|
155
|
+
### Languages
|
|
156
|
+
|
|
157
|
+
```typescript
|
|
158
|
+
interface Language {
|
|
159
|
+
code: string; // e.g., 'en', 'it', 'de'
|
|
160
|
+
label: string; // e.g., 'English', 'Italiano', 'Deutsch'
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Default languages if not specified
|
|
164
|
+
const DEFAULT_LANGUAGES = [
|
|
165
|
+
{ code: 'en', label: 'English' },
|
|
166
|
+
{ code: 'it', label: 'Italiano' },
|
|
167
|
+
];
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### Image Options
|
|
171
|
+
|
|
172
|
+
```typescript
|
|
173
|
+
interface ImageOptions {
|
|
174
|
+
// Required: Fetch paginated list of images
|
|
175
|
+
fetchList: (params: FetchListParams) => Promise<ImageItem[] | FetchListResult<ImageItem>>;
|
|
176
|
+
|
|
177
|
+
// Optional: Upload new images
|
|
178
|
+
upload?: (file: File, callbacks: UploadCallbacks) => Promise<ImageItem | ImageItem[]>;
|
|
179
|
+
|
|
180
|
+
// Optional: Update image metadata (alt text)
|
|
181
|
+
update?: (id: string, data: { alt?: LocalizedString }) => Promise<ImageItem>;
|
|
182
|
+
|
|
183
|
+
// Optional: Delete an image (enables Manage mode)
|
|
184
|
+
delete?: (id: string) => Promise<void>;
|
|
185
|
+
|
|
186
|
+
// Optional: Upload configuration
|
|
187
|
+
uploadConfig?: UploadConfig;
|
|
188
|
+
}
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### Gallery Options
|
|
192
|
+
|
|
193
|
+
```typescript
|
|
194
|
+
interface GalleryOptions {
|
|
195
|
+
// Required: Fetch paginated list of galleries
|
|
196
|
+
fetchList: (params: FetchListParams) => Promise<GalleryItem[] | FetchListResult<GalleryItem>>;
|
|
197
|
+
|
|
198
|
+
// Required: Fetch single gallery with all images
|
|
199
|
+
fetch: (id: string) => Promise<GalleryItem>;
|
|
200
|
+
|
|
201
|
+
// Required: Create a new gallery
|
|
202
|
+
create: (name: string) => Promise<GalleryItem>;
|
|
203
|
+
|
|
204
|
+
// Optional: Delete a gallery (enables Manage mode)
|
|
205
|
+
delete?: (id: string) => Promise<void>;
|
|
206
|
+
|
|
207
|
+
// Required: Upload images to a gallery
|
|
208
|
+
upload: (galleryId: string, file: File, callbacks: UploadCallbacks) => Promise<ImageItem | ImageItem[]>;
|
|
209
|
+
|
|
210
|
+
// Optional: Remove an image from a gallery (enables image Manage mode)
|
|
211
|
+
removeImage?: (galleryId: string, imageId: string) => Promise<void>;
|
|
212
|
+
|
|
213
|
+
// Optional: Update image metadata within a gallery
|
|
214
|
+
updateImage?: (galleryId: string, imageId: string, data: { alt?: LocalizedString }) => Promise<ImageItem>;
|
|
215
|
+
}
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
### Document Options
|
|
219
|
+
|
|
220
|
+
```typescript
|
|
221
|
+
interface DocumentOptions {
|
|
222
|
+
// Required: Fetch paginated list of documents
|
|
223
|
+
fetchList: (params: FetchListParams) => Promise<DocumentItem[] | FetchListResult<DocumentItem>>;
|
|
224
|
+
|
|
225
|
+
// Optional: Upload new documents
|
|
226
|
+
upload?: (file: File, callbacks: UploadCallbacks) => Promise<DocumentItem | DocumentItem[]>;
|
|
227
|
+
|
|
228
|
+
// Optional: Update document metadata (title)
|
|
229
|
+
update?: (id: string, data: { title?: LocalizedString }) => Promise<DocumentItem>;
|
|
230
|
+
|
|
231
|
+
// Optional: Delete a document (enables Manage mode)
|
|
232
|
+
delete?: (id: string) => Promise<void>;
|
|
233
|
+
|
|
234
|
+
// Optional: Upload configuration
|
|
235
|
+
uploadConfig?: UploadConfig;
|
|
236
|
+
}
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
### Upload Configuration
|
|
240
|
+
|
|
241
|
+
```typescript
|
|
242
|
+
interface UploadConfig {
|
|
243
|
+
// Accepted file types (default: 'image/*' for images, common doc types for documents)
|
|
244
|
+
accept?: string;
|
|
245
|
+
|
|
246
|
+
// Maximum file size in bytes (default: 10MB for images, 20MB for documents)
|
|
247
|
+
maxSize?: number;
|
|
248
|
+
|
|
249
|
+
// Allow multiple file selection (default: true)
|
|
250
|
+
multiple?: boolean;
|
|
251
|
+
}
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
## Types
|
|
255
|
+
|
|
256
|
+
### ImageItem
|
|
257
|
+
|
|
258
|
+
```typescript
|
|
259
|
+
interface ImageItem {
|
|
260
|
+
id: string;
|
|
261
|
+
url: string;
|
|
262
|
+
filename?: string;
|
|
263
|
+
alt?: LocalizedString; // { en: 'Alt text', it: 'Testo alt' }
|
|
264
|
+
width?: number;
|
|
265
|
+
height?: number;
|
|
266
|
+
size?: number; // File size in bytes
|
|
267
|
+
thumbnailUrl?: string; // For faster loading in grids
|
|
268
|
+
createdAt?: string;
|
|
269
|
+
}
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
### GalleryItem
|
|
273
|
+
|
|
274
|
+
```typescript
|
|
275
|
+
interface GalleryItem {
|
|
276
|
+
id: string;
|
|
277
|
+
name: string;
|
|
278
|
+
coverImage?: ImageItem;
|
|
279
|
+
images: ImageItem[];
|
|
280
|
+
imageCount?: number;
|
|
281
|
+
createdAt?: string;
|
|
282
|
+
}
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
### DocumentItem
|
|
286
|
+
|
|
287
|
+
```typescript
|
|
288
|
+
interface DocumentItem {
|
|
289
|
+
id: string;
|
|
290
|
+
url: string;
|
|
291
|
+
filename: string;
|
|
292
|
+
title?: LocalizedString; // { en: 'Title', it: 'Titolo' }
|
|
293
|
+
mimeType: string; // e.g., 'application/pdf'
|
|
294
|
+
size: number; // File size in bytes
|
|
295
|
+
extension: string; // e.g., 'pdf', 'docx'
|
|
296
|
+
createdAt?: string;
|
|
297
|
+
}
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
### LocalizedString
|
|
301
|
+
|
|
302
|
+
```typescript
|
|
303
|
+
interface LocalizedString {
|
|
304
|
+
[languageCode: string]: string;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// Example
|
|
308
|
+
const alt: LocalizedString = {
|
|
309
|
+
en: 'Coastal sunset',
|
|
310
|
+
it: 'Tramonto sulla costa',
|
|
311
|
+
};
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
### FetchListParams
|
|
315
|
+
|
|
316
|
+
```typescript
|
|
317
|
+
interface FetchListParams {
|
|
318
|
+
query?: string; // Search query
|
|
319
|
+
page?: number; // Page number (1-indexed)
|
|
320
|
+
pageSize?: number; // Items per page (default: 20)
|
|
321
|
+
}
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
### FetchListResult
|
|
325
|
+
|
|
326
|
+
```typescript
|
|
327
|
+
interface FetchListResult<T> {
|
|
328
|
+
items: T[];
|
|
329
|
+
total?: number;
|
|
330
|
+
hasMore?: boolean;
|
|
331
|
+
}
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
### UploadCallbacks
|
|
335
|
+
|
|
336
|
+
```typescript
|
|
337
|
+
interface UploadCallbacks {
|
|
338
|
+
onProgress?: (percent: number) => void;
|
|
339
|
+
}
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
## Components
|
|
343
|
+
|
|
344
|
+
For advanced customization, you can import individual components:
|
|
345
|
+
|
|
346
|
+
```typescript
|
|
347
|
+
import {
|
|
348
|
+
// Fields (for custom block configurations)
|
|
349
|
+
ImageField,
|
|
350
|
+
GalleryField,
|
|
351
|
+
DocumentField,
|
|
352
|
+
|
|
353
|
+
// Modals (for custom implementations)
|
|
354
|
+
ImagePickerModal,
|
|
355
|
+
GalleryPickerModal,
|
|
356
|
+
DocumentPickerModal,
|
|
357
|
+
|
|
358
|
+
// Media Panel (shown in Puck sidebar)
|
|
359
|
+
MediaPanel,
|
|
360
|
+
|
|
361
|
+
// Upload components
|
|
362
|
+
UploadDropzone,
|
|
363
|
+
UploadQueue,
|
|
364
|
+
|
|
365
|
+
// Hook
|
|
366
|
+
useUpload,
|
|
367
|
+
formatFileSize,
|
|
368
|
+
} from '@caprionlinesrl/puck-plugin-media';
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
## Styling
|
|
372
|
+
|
|
373
|
+
The plugin uses CSS Modules with vanilla CSS that matches Puck's design language. Import the styles in your app:
|
|
374
|
+
|
|
375
|
+
```tsx
|
|
376
|
+
import '@caprionlinesrl/puck-plugin-media/styles.css';
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
## Browser Support
|
|
380
|
+
|
|
381
|
+
- Chrome, Firefox, Safari, Edge (latest versions)
|
|
382
|
+
- React 18+
|
|
383
|
+
- Puck 0.21+
|
|
384
|
+
|
|
385
|
+
## Development
|
|
386
|
+
|
|
387
|
+
### Prerequisites
|
|
388
|
+
|
|
389
|
+
- Node.js 18+
|
|
390
|
+
- Yarn 4+
|
|
391
|
+
|
|
392
|
+
### Setup
|
|
393
|
+
|
|
394
|
+
```bash
|
|
395
|
+
git clone https://github.com/caprionlinesrl/puck-plugin-media.git
|
|
396
|
+
cd puck-plugin-media
|
|
397
|
+
yarn install
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
### Run the demo
|
|
401
|
+
|
|
402
|
+
```bash
|
|
403
|
+
yarn demo
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
Open [http://localhost:3000](http://localhost:3000)
|
|
407
|
+
|
|
408
|
+
### Build the library
|
|
409
|
+
|
|
410
|
+
```bash
|
|
411
|
+
yarn build
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
### Type check
|
|
415
|
+
|
|
416
|
+
```bash
|
|
417
|
+
yarn typecheck
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
## License
|
|
421
|
+
|
|
422
|
+
MIT
|