@btst/stack 2.8.1 → 2.9.1
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/README.md +3 -2
- package/dist/components/markdown/index.d.cts +15 -2
- package/dist/components/markdown/index.d.mts +15 -2
- package/dist/components/markdown/index.d.ts +15 -2
- package/dist/packages/stack/src/plugins/blog/client/components/forms/image-field.cjs +30 -1
- package/dist/packages/stack/src/plugins/blog/client/components/forms/image-field.mjs +30 -1
- package/dist/packages/stack/src/plugins/blog/client/components/forms/markdown-editor-with-overrides.cjs +49 -9
- package/dist/packages/stack/src/plugins/blog/client/components/forms/markdown-editor-with-overrides.mjs +50 -10
- package/dist/packages/stack/src/plugins/blog/client/components/forms/markdown-editor.cjs +77 -9
- package/dist/packages/stack/src/plugins/blog/client/components/forms/markdown-editor.mjs +77 -9
- package/dist/packages/stack/src/plugins/cms/client/components/forms/content-form.cjs +24 -5
- package/dist/packages/stack/src/plugins/cms/client/components/forms/content-form.mjs +24 -5
- package/dist/packages/stack/src/plugins/cms/client/components/forms/file-upload.cjs +47 -13
- package/dist/packages/stack/src/plugins/cms/client/components/forms/file-upload.mjs +47 -13
- package/dist/packages/stack/src/plugins/kanban/client/components/forms/board-form.cjs +1 -1
- package/dist/packages/stack/src/plugins/kanban/client/components/forms/board-form.mjs +1 -1
- package/dist/packages/stack/src/plugins/kanban/client/components/forms/task-form.cjs +6 -2
- package/dist/packages/stack/src/plugins/kanban/client/components/forms/task-form.mjs +6 -2
- package/dist/packages/stack/src/plugins/media/api/adapters/local.cjs +55 -0
- package/dist/packages/stack/src/plugins/media/api/adapters/local.mjs +37 -0
- package/dist/packages/stack/src/plugins/media/api/getters.cjs +83 -0
- package/dist/packages/stack/src/plugins/media/api/getters.mjs +78 -0
- package/dist/packages/stack/src/plugins/media/api/mutations.cjs +88 -0
- package/dist/packages/stack/src/plugins/media/api/mutations.mjs +82 -0
- package/dist/packages/stack/src/plugins/media/api/plugin.cjs +525 -0
- package/dist/packages/stack/src/plugins/media/api/plugin.mjs +523 -0
- package/dist/packages/stack/src/plugins/media/api/query-key-defs.cjs +19 -0
- package/dist/packages/stack/src/plugins/media/api/query-key-defs.mjs +16 -0
- package/dist/packages/stack/src/plugins/media/api/serializers.cjs +17 -0
- package/dist/packages/stack/src/plugins/media/api/serializers.mjs +14 -0
- package/dist/packages/stack/src/plugins/media/api/storage-adapter.cjs +15 -0
- package/dist/packages/stack/src/plugins/media/api/storage-adapter.mjs +11 -0
- package/dist/packages/stack/src/plugins/media/client/components/media-picker/asset-card.cjs +129 -0
- package/dist/packages/stack/src/plugins/media/client/components/media-picker/asset-card.mjs +127 -0
- package/dist/packages/stack/src/plugins/media/client/components/media-picker/asset-preview-button.cjs +58 -0
- package/dist/packages/stack/src/plugins/media/client/components/media-picker/asset-preview-button.mjs +56 -0
- package/dist/packages/stack/src/plugins/media/client/components/media-picker/browse-tab.cjs +94 -0
- package/dist/packages/stack/src/plugins/media/client/components/media-picker/browse-tab.mjs +92 -0
- package/dist/packages/stack/src/plugins/media/client/components/media-picker/folder-tree.cjs +171 -0
- package/dist/packages/stack/src/plugins/media/client/components/media-picker/folder-tree.mjs +168 -0
- package/dist/packages/stack/src/plugins/media/client/components/media-picker/index.cjs +308 -0
- package/dist/packages/stack/src/plugins/media/client/components/media-picker/index.mjs +305 -0
- package/dist/packages/stack/src/plugins/media/client/components/media-picker/upload-tab.cjs +104 -0
- package/dist/packages/stack/src/plugins/media/client/components/media-picker/upload-tab.mjs +102 -0
- package/dist/packages/stack/src/plugins/media/client/components/media-picker/url-tab.cjs +70 -0
- package/dist/packages/stack/src/plugins/media/client/components/media-picker/url-tab.mjs +68 -0
- package/dist/packages/stack/src/plugins/media/client/components/media-picker/utils.cjs +21 -0
- package/dist/packages/stack/src/plugins/media/client/components/media-picker/utils.mjs +17 -0
- package/dist/packages/stack/src/plugins/media/client/components/pages/library-page.cjs +35 -0
- package/dist/packages/stack/src/plugins/media/client/components/pages/library-page.internal.cjs +125 -0
- package/dist/packages/stack/src/plugins/media/client/components/pages/library-page.internal.mjs +123 -0
- package/dist/packages/stack/src/plugins/media/client/components/pages/library-page.mjs +33 -0
- package/dist/packages/stack/src/plugins/media/client/hooks/use-media.cjs +222 -0
- package/dist/packages/stack/src/plugins/media/client/hooks/use-media.mjs +214 -0
- package/dist/packages/stack/src/plugins/media/client/plugin.cjs +94 -0
- package/dist/packages/stack/src/plugins/media/client/plugin.mjs +92 -0
- package/dist/packages/stack/src/plugins/media/client/upload.cjs +121 -0
- package/dist/packages/stack/src/plugins/media/client/upload.mjs +119 -0
- package/dist/packages/stack/src/plugins/media/client/utils/image-compression.cjs +67 -0
- package/dist/packages/stack/src/plugins/media/client/utils/image-compression.mjs +65 -0
- package/dist/packages/stack/src/plugins/media/db.cjs +62 -0
- package/dist/packages/stack/src/plugins/media/db.mjs +60 -0
- package/dist/packages/stack/src/plugins/media/schemas.cjs +41 -0
- package/dist/packages/stack/src/plugins/media/schemas.mjs +35 -0
- package/dist/packages/ui/src/components/minimal-tiptap/components/image/image-edit-block.cjs +18 -1
- package/dist/packages/ui/src/components/minimal-tiptap/components/image/image-edit-block.mjs +19 -2
- package/dist/packages/ui/src/components/minimal-tiptap/components/image/image-edit-dialog.cjs +2 -2
- package/dist/packages/ui/src/components/minimal-tiptap/components/image/image-edit-dialog.mjs +2 -2
- package/dist/packages/ui/src/components/minimal-tiptap/components/section/five.cjs +3 -2
- package/dist/packages/ui/src/components/minimal-tiptap/components/section/five.mjs +3 -2
- package/dist/packages/ui/src/components/minimal-tiptap/minimal-tiptap.cjs +12 -5
- package/dist/packages/ui/src/components/minimal-tiptap/minimal-tiptap.mjs +12 -5
- package/dist/plugins/blog/client/index.d.cts +58 -1
- package/dist/plugins/blog/client/index.d.mts +58 -1
- package/dist/plugins/blog/client/index.d.ts +58 -1
- package/dist/plugins/cms/client/index.d.cts +73 -3
- package/dist/plugins/cms/client/index.d.mts +73 -3
- package/dist/plugins/cms/client/index.d.ts +73 -3
- package/dist/plugins/kanban/api/index.d.cts +1 -1
- package/dist/plugins/kanban/api/index.d.mts +1 -1
- package/dist/plugins/kanban/api/index.d.ts +1 -1
- package/dist/plugins/kanban/client/hooks/index.d.cts +1 -1
- package/dist/plugins/kanban/client/hooks/index.d.mts +1 -1
- package/dist/plugins/kanban/client/hooks/index.d.ts +1 -1
- package/dist/plugins/kanban/client/index.d.cts +1 -1
- package/dist/plugins/kanban/client/index.d.mts +1 -1
- package/dist/plugins/kanban/client/index.d.ts +1 -1
- package/dist/plugins/kanban/query-keys.d.cts +1 -1
- package/dist/plugins/kanban/query-keys.d.mts +1 -1
- package/dist/plugins/kanban/query-keys.d.ts +1 -1
- package/dist/plugins/media/api/adapters/s3.cjs +106 -0
- package/dist/plugins/media/api/adapters/s3.d.cts +60 -0
- package/dist/plugins/media/api/adapters/s3.d.mts +60 -0
- package/dist/plugins/media/api/adapters/s3.d.ts +60 -0
- package/dist/plugins/media/api/adapters/s3.mjs +104 -0
- package/dist/plugins/media/api/adapters/vercel-blob.cjs +53 -0
- package/dist/plugins/media/api/adapters/vercel-blob.d.cts +41 -0
- package/dist/plugins/media/api/adapters/vercel-blob.d.mts +41 -0
- package/dist/plugins/media/api/adapters/vercel-blob.d.ts +41 -0
- package/dist/plugins/media/api/adapters/vercel-blob.mjs +51 -0
- package/dist/plugins/media/api/index.cjs +26 -0
- package/dist/plugins/media/api/index.d.cts +116 -0
- package/dist/plugins/media/api/index.d.mts +116 -0
- package/dist/plugins/media/api/index.d.ts +116 -0
- package/dist/plugins/media/api/index.mjs +6 -0
- package/dist/plugins/media/client/components/index.cjs +10 -0
- package/dist/plugins/media/client/components/index.d.cts +55 -0
- package/dist/plugins/media/client/components/index.d.mts +55 -0
- package/dist/plugins/media/client/components/index.d.ts +55 -0
- package/dist/plugins/media/client/components/index.mjs +2 -0
- package/dist/plugins/media/client/hooks/index.cjs +13 -0
- package/dist/plugins/media/client/hooks/index.d.cts +53 -0
- package/dist/plugins/media/client/hooks/index.d.mts +53 -0
- package/dist/plugins/media/client/hooks/index.d.ts +53 -0
- package/dist/plugins/media/client/hooks/index.mjs +1 -0
- package/dist/plugins/media/client/index.cjs +9 -0
- package/dist/plugins/media/client/index.d.cts +242 -0
- package/dist/plugins/media/client/index.d.mts +242 -0
- package/dist/plugins/media/client/index.d.ts +242 -0
- package/dist/plugins/media/client/index.mjs +2 -0
- package/dist/plugins/media/client.css +1 -0
- package/dist/plugins/media/query-keys.cjs +72 -0
- package/dist/plugins/media/query-keys.d.cts +49 -0
- package/dist/plugins/media/query-keys.d.mts +49 -0
- package/dist/plugins/media/query-keys.d.ts +49 -0
- package/dist/plugins/media/query-keys.mjs +70 -0
- package/dist/plugins/media/style.css +1 -0
- package/dist/shared/{stack.DRpeDS6X.d.ts → stack.BMx2QYOK.d.ts} +25 -0
- package/dist/shared/stack.BttDsJJn.d.cts +109 -0
- package/dist/shared/stack.BttDsJJn.d.mts +109 -0
- package/dist/shared/stack.BttDsJJn.d.ts +109 -0
- package/dist/shared/stack.C7vfOBmO.d.mts +63 -0
- package/dist/shared/stack.CAni8dnD.d.cts +63 -0
- package/dist/shared/stack.CI8iRKKi.d.cts +286 -0
- package/dist/shared/stack.CLcnSF_b.d.cts +25 -0
- package/dist/shared/stack.CLcnSF_b.d.mts +25 -0
- package/dist/shared/stack.CLcnSF_b.d.ts +25 -0
- package/dist/shared/stack.CYSwntXC.d.ts +63 -0
- package/dist/shared/{stack.Jb0kQDJC.d.mts → stack.Cd6McBu1.d.mts} +25 -0
- package/dist/shared/stack.DJDjdG64.d.ts +286 -0
- package/dist/shared/{stack.BxFl46lB.d.cts → stack.DxQl8Wa1.d.cts} +25 -0
- package/dist/shared/stack.FgBVDSPi.d.mts +286 -0
- package/package.json +113 -4
- package/src/plugins/blog/client/components/forms/image-field.tsx +35 -4
- package/src/plugins/blog/client/components/forms/markdown-editor-with-overrides.tsx +67 -12
- package/src/plugins/blog/client/components/forms/markdown-editor.tsx +106 -10
- package/src/plugins/blog/client/overrides.ts +58 -1
- package/src/plugins/cms/client/components/forms/content-form.tsx +26 -7
- package/src/plugins/cms/client/components/forms/file-upload.tsx +73 -15
- package/src/plugins/cms/client/overrides.ts +57 -2
- package/src/plugins/kanban/client/components/forms/board-form.tsx +1 -1
- package/src/plugins/kanban/client/components/forms/task-form.tsx +7 -1
- package/src/plugins/kanban/client/overrides.ts +25 -0
- package/src/plugins/media/__tests__/__stubs__/vercel-blob-server.ts +9 -0
- package/src/plugins/media/__tests__/getters.test.ts +274 -0
- package/src/plugins/media/__tests__/mutations.test.ts +299 -0
- package/src/plugins/media/__tests__/plugin.test.ts +752 -0
- package/src/plugins/media/__tests__/query-key-defs.test.ts +54 -0
- package/src/plugins/media/__tests__/storage-adapters.test.ts +351 -0
- package/src/plugins/media/api/adapters/local.ts +79 -0
- package/src/plugins/media/api/adapters/s3.ts +198 -0
- package/src/plugins/media/api/adapters/vercel-blob.ts +131 -0
- package/src/plugins/media/api/getters.ts +174 -0
- package/src/plugins/media/api/index.ts +41 -0
- package/src/plugins/media/api/mutations.ts +179 -0
- package/src/plugins/media/api/plugin.ts +855 -0
- package/src/plugins/media/api/query-key-defs.ts +41 -0
- package/src/plugins/media/api/serializers.ts +28 -0
- package/src/plugins/media/api/storage-adapter.ts +139 -0
- package/src/plugins/media/client/components/index.tsx +6 -0
- package/src/plugins/media/client/components/media-picker/asset-card.tsx +150 -0
- package/src/plugins/media/client/components/media-picker/asset-preview-button.tsx +67 -0
- package/src/plugins/media/client/components/media-picker/browse-tab.tsx +116 -0
- package/src/plugins/media/client/components/media-picker/folder-tree.tsx +188 -0
- package/src/plugins/media/client/components/media-picker/index.tsx +347 -0
- package/src/plugins/media/client/components/media-picker/upload-tab.tsx +108 -0
- package/src/plugins/media/client/components/media-picker/url-tab.tsx +72 -0
- package/src/plugins/media/client/components/media-picker/utils.ts +17 -0
- package/src/plugins/media/client/components/pages/library-page.internal.tsx +134 -0
- package/src/plugins/media/client/components/pages/library-page.tsx +42 -0
- package/src/plugins/media/client/hooks/index.tsx +9 -0
- package/src/plugins/media/client/hooks/use-media.tsx +289 -0
- package/src/plugins/media/client/index.ts +4 -0
- package/src/plugins/media/client/overrides.ts +127 -0
- package/src/plugins/media/client/plugin.tsx +184 -0
- package/src/plugins/media/client/upload.ts +171 -0
- package/src/plugins/media/client/utils/image-compression.ts +131 -0
- package/src/plugins/media/client.css +1 -0
- package/src/plugins/media/db.ts +62 -0
- package/src/plugins/media/query-keys.ts +96 -0
- package/src/plugins/media/schemas.ts +37 -0
- package/src/plugins/media/style.css +1 -0
- package/src/plugins/media/types.ts +26 -0
- package/dist/shared/{stack.BOokfhZD.d.cts → stack.B6S3cgwN.d.cts} +16 -16
- package/dist/shared/{stack.CWxAl9K3.d.mts → stack.Bzfx-_lq.d.mts} +16 -16
- package/dist/shared/{stack.BvCR4-9H.d.ts → stack.j5SFLC1d.d.ts} +16 -16
|
@@ -392,6 +392,17 @@ declare const BLOG_LOCALIZATION: {
|
|
|
392
392
|
};
|
|
393
393
|
type BlogLocalization = typeof BLOG_LOCALIZATION;
|
|
394
394
|
|
|
395
|
+
/**
|
|
396
|
+
* Props for the overridable blog featured image input component.
|
|
397
|
+
*/
|
|
398
|
+
interface BlogImageInputFieldProps {
|
|
399
|
+
/** Current image URL value */
|
|
400
|
+
value: string;
|
|
401
|
+
/** Called when the image URL changes */
|
|
402
|
+
onChange: (value: string) => void;
|
|
403
|
+
/** Whether the field is required */
|
|
404
|
+
isRequired?: boolean;
|
|
405
|
+
}
|
|
395
406
|
/**
|
|
396
407
|
* Context passed to lifecycle hooks
|
|
397
408
|
*/
|
|
@@ -435,9 +446,55 @@ interface BlogPluginOverrides {
|
|
|
435
446
|
*/
|
|
436
447
|
Image?: ComponentType<React.ImgHTMLAttributes<HTMLImageElement> & Record<string, any>>;
|
|
437
448
|
/**
|
|
438
|
-
* Function used to upload
|
|
449
|
+
* Function used to upload a new image file and return its URL.
|
|
450
|
+
* This is separate from `imagePicker`, which selects an existing asset URL.
|
|
439
451
|
*/
|
|
440
452
|
uploadImage: (file: File) => Promise<string>;
|
|
453
|
+
/**
|
|
454
|
+
* Optional custom component for the featured image field.
|
|
455
|
+
*
|
|
456
|
+
* When provided it replaces the default file-upload input entirely.
|
|
457
|
+
* The component receives `value` (current URL string) and `onChange` (setter).
|
|
458
|
+
*
|
|
459
|
+
* Typical use case: render a preview when a value is set, and a media-picker
|
|
460
|
+
* trigger when no value is set.
|
|
461
|
+
*
|
|
462
|
+
* @example
|
|
463
|
+
* ```tsx
|
|
464
|
+
* imageInputField: ({ value, onChange }) =>
|
|
465
|
+
* value ? (
|
|
466
|
+
* <div>
|
|
467
|
+
* <img src={value} alt="Preview" />
|
|
468
|
+
* <MediaPicker trigger={<button>Change</button>} accept={["image/*"]}
|
|
469
|
+
* onSelect={(assets) => onChange(assets[0].url)} />
|
|
470
|
+
* </div>
|
|
471
|
+
* ) : (
|
|
472
|
+
* <MediaPicker trigger={<button>Browse media</button>} accept={["image/*"]}
|
|
473
|
+
* onSelect={(assets) => onChange(assets[0].url)} />
|
|
474
|
+
* )
|
|
475
|
+
* ```
|
|
476
|
+
*/
|
|
477
|
+
imageInputField?: ComponentType<BlogImageInputFieldProps>;
|
|
478
|
+
/**
|
|
479
|
+
* Optional trigger component for a media picker.
|
|
480
|
+
* When provided, it is rendered adjacent to the Markdown editor and allows
|
|
481
|
+
* users to browse and select previously uploaded assets.
|
|
482
|
+
* Receives `onSelect(url)` — insert the chosen URL into the editor.
|
|
483
|
+
*
|
|
484
|
+
* @example
|
|
485
|
+
* ```tsx
|
|
486
|
+
* imagePicker: ({ onSelect }) => (
|
|
487
|
+
* <MediaPicker
|
|
488
|
+
* trigger={<Button size="sm" variant="outline">Browse media</Button>}
|
|
489
|
+
* accept={["image/*"]}
|
|
490
|
+
* onSelect={(assets) => onSelect(assets[0].url)}
|
|
491
|
+
* />
|
|
492
|
+
* )
|
|
493
|
+
* ```
|
|
494
|
+
*/
|
|
495
|
+
imagePicker?: ComponentType<{
|
|
496
|
+
onSelect: (url: string) => void;
|
|
497
|
+
}>;
|
|
441
498
|
/**
|
|
442
499
|
* Localization object for the blog plugin
|
|
443
500
|
*/
|
|
@@ -261,6 +261,17 @@ declare const CMS_LOCALIZATION: {
|
|
|
261
261
|
};
|
|
262
262
|
type CMSLocalization = typeof CMS_LOCALIZATION;
|
|
263
263
|
|
|
264
|
+
/**
|
|
265
|
+
* Props for the overridable CMS image input field component.
|
|
266
|
+
*/
|
|
267
|
+
interface CmsImageInputFieldProps {
|
|
268
|
+
/** Current image URL value */
|
|
269
|
+
value: string;
|
|
270
|
+
/** Called when the image URL changes */
|
|
271
|
+
onChange: (value: string) => void;
|
|
272
|
+
/** Whether the field is required */
|
|
273
|
+
isRequired?: boolean;
|
|
274
|
+
}
|
|
264
275
|
/**
|
|
265
276
|
* Context passed to lifecycle hooks
|
|
266
277
|
*/
|
|
@@ -298,10 +309,53 @@ interface CMSPluginOverrides {
|
|
|
298
309
|
*/
|
|
299
310
|
Image?: ComponentType<React.ImgHTMLAttributes<HTMLImageElement> & Record<string, unknown>>;
|
|
300
311
|
/**
|
|
301
|
-
* Function used to upload
|
|
302
|
-
* Used by the default "file" field component
|
|
312
|
+
* Function used to upload a new image file and return its URL.
|
|
313
|
+
* Used by the default "file" field component when not selecting an existing
|
|
314
|
+
* asset via `imagePicker` or `imageInputField`.
|
|
303
315
|
*/
|
|
304
316
|
uploadImage?: (file: File) => Promise<string>;
|
|
317
|
+
/**
|
|
318
|
+
* Optional custom component for image fields (fieldType: "file").
|
|
319
|
+
*
|
|
320
|
+
* When provided it replaces the default file-upload input entirely.
|
|
321
|
+
* The component receives `value` (current URL string) and `onChange` (setter).
|
|
322
|
+
*
|
|
323
|
+
* @example
|
|
324
|
+
* ```tsx
|
|
325
|
+
* imageInputField: ({ value, onChange }) =>
|
|
326
|
+
* value ? (
|
|
327
|
+
* <div>
|
|
328
|
+
* <img src={value} alt="Preview" />
|
|
329
|
+
* <MediaPicker trigger={<button>Change</button>} accept={["image/*"]}
|
|
330
|
+
* onSelect={(assets) => onChange(assets[0].url)} />
|
|
331
|
+
* </div>
|
|
332
|
+
* ) : (
|
|
333
|
+
* <MediaPicker trigger={<button>Browse media</button>} accept={["image/*"]}
|
|
334
|
+
* onSelect={(assets) => onChange(assets[0].url)} />
|
|
335
|
+
* )
|
|
336
|
+
* ```
|
|
337
|
+
*/
|
|
338
|
+
imageInputField?: ComponentType<CmsImageInputFieldProps>;
|
|
339
|
+
/**
|
|
340
|
+
* Optional trigger component for a media picker.
|
|
341
|
+
* When provided, it is rendered inside the default "file" field component as a
|
|
342
|
+
* "Browse media" option, letting users select a previously uploaded asset.
|
|
343
|
+
* Receives `onSelect(url)` — the URL is set as the field value.
|
|
344
|
+
*
|
|
345
|
+
* @example
|
|
346
|
+
* ```tsx
|
|
347
|
+
* imagePicker: ({ onSelect }) => (
|
|
348
|
+
* <MediaPicker
|
|
349
|
+
* trigger={<Button size="sm" variant="outline">Browse media</Button>}
|
|
350
|
+
* accept={["image/*"]}
|
|
351
|
+
* onSelect={(assets) => onSelect(assets[0].url)}
|
|
352
|
+
* />
|
|
353
|
+
* )
|
|
354
|
+
* ```
|
|
355
|
+
*/
|
|
356
|
+
imagePicker?: ComponentType<{
|
|
357
|
+
onSelect: (url: string) => void;
|
|
358
|
+
}>;
|
|
305
359
|
/**
|
|
306
360
|
* Custom field components for AutoForm fields.
|
|
307
361
|
*
|
|
@@ -402,6 +456,22 @@ interface CMSFileUploadProps extends AutoFormInputComponentProps {
|
|
|
402
456
|
* This is required - consumers must provide an upload implementation.
|
|
403
457
|
*/
|
|
404
458
|
uploadImage: (file: File) => Promise<string>;
|
|
459
|
+
/**
|
|
460
|
+
* Optional custom component for the image field.
|
|
461
|
+
* When provided, it replaces the default file-upload input entirely.
|
|
462
|
+
*/
|
|
463
|
+
imageInputField?: ComponentType<{
|
|
464
|
+
value: string;
|
|
465
|
+
onChange: (value: string) => void;
|
|
466
|
+
isRequired?: boolean;
|
|
467
|
+
}>;
|
|
468
|
+
/**
|
|
469
|
+
* Optional trigger component for a media picker.
|
|
470
|
+
* When provided, it is rendered as a "Browse media" option.
|
|
471
|
+
*/
|
|
472
|
+
imagePicker?: ComponentType<{
|
|
473
|
+
onSelect: (url: string) => void;
|
|
474
|
+
}>;
|
|
405
475
|
}
|
|
406
476
|
/**
|
|
407
477
|
* Default file upload component for CMS image fields.
|
|
@@ -425,7 +495,7 @@ interface CMSFileUploadProps extends AutoFormInputComponentProps {
|
|
|
425
495
|
* }
|
|
426
496
|
* ```
|
|
427
497
|
*/
|
|
428
|
-
declare function CMSFileUpload({ label, isRequired, fieldConfigItem, fieldProps, field, uploadImage, }: CMSFileUploadProps): react_jsx_runtime.JSX.Element;
|
|
498
|
+
declare function CMSFileUpload({ label, isRequired, fieldConfigItem, fieldProps, field, uploadImage, imageInputField: ImageInputField, imagePicker: ImagePickerTrigger, }: CMSFileUploadProps): react_jsx_runtime.JSX.Element;
|
|
429
499
|
|
|
430
500
|
export { AutoFormInputComponentProps, CMSFileUpload, cmsClientPlugin };
|
|
431
501
|
export type { CMSClientConfig, CMSClientHooks, CMSFileUploadProps, CMSLocalization, CMSPluginOverrides, LoaderContext, RouteContext };
|
|
@@ -261,6 +261,17 @@ declare const CMS_LOCALIZATION: {
|
|
|
261
261
|
};
|
|
262
262
|
type CMSLocalization = typeof CMS_LOCALIZATION;
|
|
263
263
|
|
|
264
|
+
/**
|
|
265
|
+
* Props for the overridable CMS image input field component.
|
|
266
|
+
*/
|
|
267
|
+
interface CmsImageInputFieldProps {
|
|
268
|
+
/** Current image URL value */
|
|
269
|
+
value: string;
|
|
270
|
+
/** Called when the image URL changes */
|
|
271
|
+
onChange: (value: string) => void;
|
|
272
|
+
/** Whether the field is required */
|
|
273
|
+
isRequired?: boolean;
|
|
274
|
+
}
|
|
264
275
|
/**
|
|
265
276
|
* Context passed to lifecycle hooks
|
|
266
277
|
*/
|
|
@@ -298,10 +309,53 @@ interface CMSPluginOverrides {
|
|
|
298
309
|
*/
|
|
299
310
|
Image?: ComponentType<React.ImgHTMLAttributes<HTMLImageElement> & Record<string, unknown>>;
|
|
300
311
|
/**
|
|
301
|
-
* Function used to upload
|
|
302
|
-
* Used by the default "file" field component
|
|
312
|
+
* Function used to upload a new image file and return its URL.
|
|
313
|
+
* Used by the default "file" field component when not selecting an existing
|
|
314
|
+
* asset via `imagePicker` or `imageInputField`.
|
|
303
315
|
*/
|
|
304
316
|
uploadImage?: (file: File) => Promise<string>;
|
|
317
|
+
/**
|
|
318
|
+
* Optional custom component for image fields (fieldType: "file").
|
|
319
|
+
*
|
|
320
|
+
* When provided it replaces the default file-upload input entirely.
|
|
321
|
+
* The component receives `value` (current URL string) and `onChange` (setter).
|
|
322
|
+
*
|
|
323
|
+
* @example
|
|
324
|
+
* ```tsx
|
|
325
|
+
* imageInputField: ({ value, onChange }) =>
|
|
326
|
+
* value ? (
|
|
327
|
+
* <div>
|
|
328
|
+
* <img src={value} alt="Preview" />
|
|
329
|
+
* <MediaPicker trigger={<button>Change</button>} accept={["image/*"]}
|
|
330
|
+
* onSelect={(assets) => onChange(assets[0].url)} />
|
|
331
|
+
* </div>
|
|
332
|
+
* ) : (
|
|
333
|
+
* <MediaPicker trigger={<button>Browse media</button>} accept={["image/*"]}
|
|
334
|
+
* onSelect={(assets) => onChange(assets[0].url)} />
|
|
335
|
+
* )
|
|
336
|
+
* ```
|
|
337
|
+
*/
|
|
338
|
+
imageInputField?: ComponentType<CmsImageInputFieldProps>;
|
|
339
|
+
/**
|
|
340
|
+
* Optional trigger component for a media picker.
|
|
341
|
+
* When provided, it is rendered inside the default "file" field component as a
|
|
342
|
+
* "Browse media" option, letting users select a previously uploaded asset.
|
|
343
|
+
* Receives `onSelect(url)` — the URL is set as the field value.
|
|
344
|
+
*
|
|
345
|
+
* @example
|
|
346
|
+
* ```tsx
|
|
347
|
+
* imagePicker: ({ onSelect }) => (
|
|
348
|
+
* <MediaPicker
|
|
349
|
+
* trigger={<Button size="sm" variant="outline">Browse media</Button>}
|
|
350
|
+
* accept={["image/*"]}
|
|
351
|
+
* onSelect={(assets) => onSelect(assets[0].url)}
|
|
352
|
+
* />
|
|
353
|
+
* )
|
|
354
|
+
* ```
|
|
355
|
+
*/
|
|
356
|
+
imagePicker?: ComponentType<{
|
|
357
|
+
onSelect: (url: string) => void;
|
|
358
|
+
}>;
|
|
305
359
|
/**
|
|
306
360
|
* Custom field components for AutoForm fields.
|
|
307
361
|
*
|
|
@@ -402,6 +456,22 @@ interface CMSFileUploadProps extends AutoFormInputComponentProps {
|
|
|
402
456
|
* This is required - consumers must provide an upload implementation.
|
|
403
457
|
*/
|
|
404
458
|
uploadImage: (file: File) => Promise<string>;
|
|
459
|
+
/**
|
|
460
|
+
* Optional custom component for the image field.
|
|
461
|
+
* When provided, it replaces the default file-upload input entirely.
|
|
462
|
+
*/
|
|
463
|
+
imageInputField?: ComponentType<{
|
|
464
|
+
value: string;
|
|
465
|
+
onChange: (value: string) => void;
|
|
466
|
+
isRequired?: boolean;
|
|
467
|
+
}>;
|
|
468
|
+
/**
|
|
469
|
+
* Optional trigger component for a media picker.
|
|
470
|
+
* When provided, it is rendered as a "Browse media" option.
|
|
471
|
+
*/
|
|
472
|
+
imagePicker?: ComponentType<{
|
|
473
|
+
onSelect: (url: string) => void;
|
|
474
|
+
}>;
|
|
405
475
|
}
|
|
406
476
|
/**
|
|
407
477
|
* Default file upload component for CMS image fields.
|
|
@@ -425,7 +495,7 @@ interface CMSFileUploadProps extends AutoFormInputComponentProps {
|
|
|
425
495
|
* }
|
|
426
496
|
* ```
|
|
427
497
|
*/
|
|
428
|
-
declare function CMSFileUpload({ label, isRequired, fieldConfigItem, fieldProps, field, uploadImage, }: CMSFileUploadProps): react_jsx_runtime.JSX.Element;
|
|
498
|
+
declare function CMSFileUpload({ label, isRequired, fieldConfigItem, fieldProps, field, uploadImage, imageInputField: ImageInputField, imagePicker: ImagePickerTrigger, }: CMSFileUploadProps): react_jsx_runtime.JSX.Element;
|
|
429
499
|
|
|
430
500
|
export { AutoFormInputComponentProps, CMSFileUpload, cmsClientPlugin };
|
|
431
501
|
export type { CMSClientConfig, CMSClientHooks, CMSFileUploadProps, CMSLocalization, CMSPluginOverrides, LoaderContext, RouteContext };
|
|
@@ -261,6 +261,17 @@ declare const CMS_LOCALIZATION: {
|
|
|
261
261
|
};
|
|
262
262
|
type CMSLocalization = typeof CMS_LOCALIZATION;
|
|
263
263
|
|
|
264
|
+
/**
|
|
265
|
+
* Props for the overridable CMS image input field component.
|
|
266
|
+
*/
|
|
267
|
+
interface CmsImageInputFieldProps {
|
|
268
|
+
/** Current image URL value */
|
|
269
|
+
value: string;
|
|
270
|
+
/** Called when the image URL changes */
|
|
271
|
+
onChange: (value: string) => void;
|
|
272
|
+
/** Whether the field is required */
|
|
273
|
+
isRequired?: boolean;
|
|
274
|
+
}
|
|
264
275
|
/**
|
|
265
276
|
* Context passed to lifecycle hooks
|
|
266
277
|
*/
|
|
@@ -298,10 +309,53 @@ interface CMSPluginOverrides {
|
|
|
298
309
|
*/
|
|
299
310
|
Image?: ComponentType<React.ImgHTMLAttributes<HTMLImageElement> & Record<string, unknown>>;
|
|
300
311
|
/**
|
|
301
|
-
* Function used to upload
|
|
302
|
-
* Used by the default "file" field component
|
|
312
|
+
* Function used to upload a new image file and return its URL.
|
|
313
|
+
* Used by the default "file" field component when not selecting an existing
|
|
314
|
+
* asset via `imagePicker` or `imageInputField`.
|
|
303
315
|
*/
|
|
304
316
|
uploadImage?: (file: File) => Promise<string>;
|
|
317
|
+
/**
|
|
318
|
+
* Optional custom component for image fields (fieldType: "file").
|
|
319
|
+
*
|
|
320
|
+
* When provided it replaces the default file-upload input entirely.
|
|
321
|
+
* The component receives `value` (current URL string) and `onChange` (setter).
|
|
322
|
+
*
|
|
323
|
+
* @example
|
|
324
|
+
* ```tsx
|
|
325
|
+
* imageInputField: ({ value, onChange }) =>
|
|
326
|
+
* value ? (
|
|
327
|
+
* <div>
|
|
328
|
+
* <img src={value} alt="Preview" />
|
|
329
|
+
* <MediaPicker trigger={<button>Change</button>} accept={["image/*"]}
|
|
330
|
+
* onSelect={(assets) => onChange(assets[0].url)} />
|
|
331
|
+
* </div>
|
|
332
|
+
* ) : (
|
|
333
|
+
* <MediaPicker trigger={<button>Browse media</button>} accept={["image/*"]}
|
|
334
|
+
* onSelect={(assets) => onChange(assets[0].url)} />
|
|
335
|
+
* )
|
|
336
|
+
* ```
|
|
337
|
+
*/
|
|
338
|
+
imageInputField?: ComponentType<CmsImageInputFieldProps>;
|
|
339
|
+
/**
|
|
340
|
+
* Optional trigger component for a media picker.
|
|
341
|
+
* When provided, it is rendered inside the default "file" field component as a
|
|
342
|
+
* "Browse media" option, letting users select a previously uploaded asset.
|
|
343
|
+
* Receives `onSelect(url)` — the URL is set as the field value.
|
|
344
|
+
*
|
|
345
|
+
* @example
|
|
346
|
+
* ```tsx
|
|
347
|
+
* imagePicker: ({ onSelect }) => (
|
|
348
|
+
* <MediaPicker
|
|
349
|
+
* trigger={<Button size="sm" variant="outline">Browse media</Button>}
|
|
350
|
+
* accept={["image/*"]}
|
|
351
|
+
* onSelect={(assets) => onSelect(assets[0].url)}
|
|
352
|
+
* />
|
|
353
|
+
* )
|
|
354
|
+
* ```
|
|
355
|
+
*/
|
|
356
|
+
imagePicker?: ComponentType<{
|
|
357
|
+
onSelect: (url: string) => void;
|
|
358
|
+
}>;
|
|
305
359
|
/**
|
|
306
360
|
* Custom field components for AutoForm fields.
|
|
307
361
|
*
|
|
@@ -402,6 +456,22 @@ interface CMSFileUploadProps extends AutoFormInputComponentProps {
|
|
|
402
456
|
* This is required - consumers must provide an upload implementation.
|
|
403
457
|
*/
|
|
404
458
|
uploadImage: (file: File) => Promise<string>;
|
|
459
|
+
/**
|
|
460
|
+
* Optional custom component for the image field.
|
|
461
|
+
* When provided, it replaces the default file-upload input entirely.
|
|
462
|
+
*/
|
|
463
|
+
imageInputField?: ComponentType<{
|
|
464
|
+
value: string;
|
|
465
|
+
onChange: (value: string) => void;
|
|
466
|
+
isRequired?: boolean;
|
|
467
|
+
}>;
|
|
468
|
+
/**
|
|
469
|
+
* Optional trigger component for a media picker.
|
|
470
|
+
* When provided, it is rendered as a "Browse media" option.
|
|
471
|
+
*/
|
|
472
|
+
imagePicker?: ComponentType<{
|
|
473
|
+
onSelect: (url: string) => void;
|
|
474
|
+
}>;
|
|
405
475
|
}
|
|
406
476
|
/**
|
|
407
477
|
* Default file upload component for CMS image fields.
|
|
@@ -425,7 +495,7 @@ interface CMSFileUploadProps extends AutoFormInputComponentProps {
|
|
|
425
495
|
* }
|
|
426
496
|
* ```
|
|
427
497
|
*/
|
|
428
|
-
declare function CMSFileUpload({ label, isRequired, fieldConfigItem, fieldProps, field, uploadImage, }: CMSFileUploadProps): react_jsx_runtime.JSX.Element;
|
|
498
|
+
declare function CMSFileUpload({ label, isRequired, fieldConfigItem, fieldProps, field, uploadImage, imageInputField: ImageInputField, imagePicker: ImagePickerTrigger, }: CMSFileUploadProps): react_jsx_runtime.JSX.Element;
|
|
429
499
|
|
|
430
500
|
export { AutoFormInputComponentProps, CMSFileUpload, cmsClientPlugin };
|
|
431
501
|
export type { CMSClientConfig, CMSClientHooks, CMSFileUploadProps, CMSLocalization, CMSPluginOverrides, LoaderContext, RouteContext };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { B as BoardListResult, C as CreateKanbanTaskInput, i as KANBAN_QUERY_KEYS, b as KanbanApiContext, K as KanbanApiRouter, c as KanbanBackendHooks, a as KanbanRouteKey, e as createKanbanTask, f as findOrCreateKanbanBoard, g as getAllBoards, d as getBoardById, h as getKanbanColumnsByBoardId, k as kanbanBackendPlugin } from '../../../shared/stack.
|
|
1
|
+
export { B as BoardListResult, C as CreateKanbanTaskInput, i as KANBAN_QUERY_KEYS, b as KanbanApiContext, K as KanbanApiRouter, c as KanbanBackendHooks, a as KanbanRouteKey, e as createKanbanTask, f as findOrCreateKanbanBoard, g as getAllBoards, d as getBoardById, h as getKanbanColumnsByBoardId, k as kanbanBackendPlugin } from '../../../shared/stack.B6S3cgwN.cjs';
|
|
2
2
|
import { B as BoardWithColumns, S as SerializedBoardWithColumns, C as ColumnWithTasks, a as SerializedColumn, T as Task, b as SerializedTask } from '../../../shared/stack.DJaKVY7v.cjs';
|
|
3
3
|
import '@btst/stack/plugins/api';
|
|
4
4
|
import '@btst/db';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { B as BoardListResult, C as CreateKanbanTaskInput, i as KANBAN_QUERY_KEYS, b as KanbanApiContext, K as KanbanApiRouter, c as KanbanBackendHooks, a as KanbanRouteKey, e as createKanbanTask, f as findOrCreateKanbanBoard, g as getAllBoards, d as getBoardById, h as getKanbanColumnsByBoardId, k as kanbanBackendPlugin } from '../../../shared/stack.
|
|
1
|
+
export { B as BoardListResult, C as CreateKanbanTaskInput, i as KANBAN_QUERY_KEYS, b as KanbanApiContext, K as KanbanApiRouter, c as KanbanBackendHooks, a as KanbanRouteKey, e as createKanbanTask, f as findOrCreateKanbanBoard, g as getAllBoards, d as getBoardById, h as getKanbanColumnsByBoardId, k as kanbanBackendPlugin } from '../../../shared/stack.Bzfx-_lq.mjs';
|
|
2
2
|
import { B as BoardWithColumns, S as SerializedBoardWithColumns, C as ColumnWithTasks, a as SerializedColumn, T as Task, b as SerializedTask } from '../../../shared/stack.DJaKVY7v.mjs';
|
|
3
3
|
import '@btst/stack/plugins/api';
|
|
4
4
|
import '@btst/db';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { B as BoardListResult, C as CreateKanbanTaskInput, i as KANBAN_QUERY_KEYS, b as KanbanApiContext, K as KanbanApiRouter, c as KanbanBackendHooks, a as KanbanRouteKey, e as createKanbanTask, f as findOrCreateKanbanBoard, g as getAllBoards, d as getBoardById, h as getKanbanColumnsByBoardId, k as kanbanBackendPlugin } from '../../../shared/stack.
|
|
1
|
+
export { B as BoardListResult, C as CreateKanbanTaskInput, i as KANBAN_QUERY_KEYS, b as KanbanApiContext, K as KanbanApiRouter, c as KanbanBackendHooks, a as KanbanRouteKey, e as createKanbanTask, f as findOrCreateKanbanBoard, g as getAllBoards, d as getBoardById, h as getKanbanColumnsByBoardId, k as kanbanBackendPlugin } from '../../../shared/stack.j5SFLC1d.js';
|
|
2
2
|
import { B as BoardWithColumns, S as SerializedBoardWithColumns, C as ColumnWithTasks, a as SerializedColumn, T as Task, b as SerializedTask } from '../../../shared/stack.DJaKVY7v.js';
|
|
3
3
|
import '@btst/stack/plugins/api';
|
|
4
4
|
import '@btst/db';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as _tanstack_react_query from '@tanstack/react-query';
|
|
2
|
-
import { a as KanbanUser } from '../../../../shared/stack.
|
|
2
|
+
import { a as KanbanUser } from '../../../../shared/stack.DxQl8Wa1.cjs';
|
|
3
3
|
import { S as SerializedBoardWithColumns, c as SerializedBoard, a as SerializedColumn, b as SerializedTask, P as Priority } from '../../../../shared/stack.DJaKVY7v.cjs';
|
|
4
4
|
import 'react';
|
|
5
5
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as _tanstack_react_query from '@tanstack/react-query';
|
|
2
|
-
import { a as KanbanUser } from '../../../../shared/stack.
|
|
2
|
+
import { a as KanbanUser } from '../../../../shared/stack.Cd6McBu1.mjs';
|
|
3
3
|
import { S as SerializedBoardWithColumns, c as SerializedBoard, a as SerializedColumn, b as SerializedTask, P as Priority } from '../../../../shared/stack.DJaKVY7v.mjs';
|
|
4
4
|
import 'react';
|
|
5
5
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as _tanstack_react_query from '@tanstack/react-query';
|
|
2
|
-
import { a as KanbanUser } from '../../../../shared/stack.
|
|
2
|
+
import { a as KanbanUser } from '../../../../shared/stack.BMx2QYOK.js';
|
|
3
3
|
import { S as SerializedBoardWithColumns, c as SerializedBoard, a as SerializedColumn, b as SerializedTask, P as Priority } from '../../../../shared/stack.DJaKVY7v.js';
|
|
4
4
|
import 'react';
|
|
5
5
|
|
|
@@ -3,7 +3,7 @@ import * as _btst_yar from '@btst/yar';
|
|
|
3
3
|
import { ComponentType } from 'react';
|
|
4
4
|
import { QueryClient } from '@tanstack/react-query';
|
|
5
5
|
import { S as SerializedBoardWithColumns } from '../../../shared/stack.DJaKVY7v.cjs';
|
|
6
|
-
export { K as KanbanPluginOverrides, a as KanbanUser } from '../../../shared/stack.
|
|
6
|
+
export { K as KanbanPluginOverrides, a as KanbanUser } from '../../../shared/stack.DxQl8Wa1.cjs';
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Context passed to route hooks
|
|
@@ -3,7 +3,7 @@ import * as _btst_yar from '@btst/yar';
|
|
|
3
3
|
import { ComponentType } from 'react';
|
|
4
4
|
import { QueryClient } from '@tanstack/react-query';
|
|
5
5
|
import { S as SerializedBoardWithColumns } from '../../../shared/stack.DJaKVY7v.mjs';
|
|
6
|
-
export { K as KanbanPluginOverrides, a as KanbanUser } from '../../../shared/stack.
|
|
6
|
+
export { K as KanbanPluginOverrides, a as KanbanUser } from '../../../shared/stack.Cd6McBu1.mjs';
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Context passed to route hooks
|
|
@@ -3,7 +3,7 @@ import * as _btst_yar from '@btst/yar';
|
|
|
3
3
|
import { ComponentType } from 'react';
|
|
4
4
|
import { QueryClient } from '@tanstack/react-query';
|
|
5
5
|
import { S as SerializedBoardWithColumns } from '../../../shared/stack.DJaKVY7v.js';
|
|
6
|
-
export { K as KanbanPluginOverrides, a as KanbanUser } from '../../../shared/stack.
|
|
6
|
+
export { K as KanbanPluginOverrides, a as KanbanUser } from '../../../shared/stack.BMx2QYOK.js';
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Context passed to route hooks
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as _tanstack_react_query from '@tanstack/react-query';
|
|
2
|
-
import { K as KanbanApiRouter, j as BoardsListDiscriminator } from '../../shared/stack.
|
|
2
|
+
import { K as KanbanApiRouter, j as BoardsListDiscriminator } from '../../shared/stack.B6S3cgwN.cjs';
|
|
3
3
|
import { createApiClient } from '@btst/stack/plugins/client';
|
|
4
4
|
import { S as SerializedBoardWithColumns } from '../../shared/stack.DJaKVY7v.cjs';
|
|
5
5
|
import '@btst/stack/plugins/api';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as _tanstack_react_query from '@tanstack/react-query';
|
|
2
|
-
import { K as KanbanApiRouter, j as BoardsListDiscriminator } from '../../shared/stack.
|
|
2
|
+
import { K as KanbanApiRouter, j as BoardsListDiscriminator } from '../../shared/stack.Bzfx-_lq.mjs';
|
|
3
3
|
import { createApiClient } from '@btst/stack/plugins/client';
|
|
4
4
|
import { S as SerializedBoardWithColumns } from '../../shared/stack.DJaKVY7v.mjs';
|
|
5
5
|
import '@btst/stack/plugins/api';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as _tanstack_react_query from '@tanstack/react-query';
|
|
2
|
-
import { K as KanbanApiRouter, j as BoardsListDiscriminator } from '../../shared/stack.
|
|
2
|
+
import { K as KanbanApiRouter, j as BoardsListDiscriminator } from '../../shared/stack.j5SFLC1d.js';
|
|
3
3
|
import { createApiClient } from '@btst/stack/plugins/client';
|
|
4
4
|
import { S as SerializedBoardWithColumns } from '../../shared/stack.DJaKVY7v.js';
|
|
5
5
|
import '@btst/stack/plugins/api';
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
function s3Adapter(options) {
|
|
4
|
+
const {
|
|
5
|
+
bucket,
|
|
6
|
+
region,
|
|
7
|
+
accessKeyId,
|
|
8
|
+
secretAccessKey,
|
|
9
|
+
endpoint,
|
|
10
|
+
publicBaseUrl,
|
|
11
|
+
expiresIn = 300
|
|
12
|
+
} = options;
|
|
13
|
+
let s3ModulePromise = null;
|
|
14
|
+
let clientPromise = null;
|
|
15
|
+
function getS3Module() {
|
|
16
|
+
if (!s3ModulePromise) {
|
|
17
|
+
s3ModulePromise = import('@aws-sdk/client-s3').catch(() => {
|
|
18
|
+
s3ModulePromise = null;
|
|
19
|
+
throw new Error(
|
|
20
|
+
"[@btst/stack] S3 adapter requires '@aws-sdk/client-s3'. Run: npm install @aws-sdk/client-s3 @aws-sdk/s3-request-presigner"
|
|
21
|
+
);
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
return s3ModulePromise;
|
|
25
|
+
}
|
|
26
|
+
function getClient() {
|
|
27
|
+
if (!clientPromise) {
|
|
28
|
+
clientPromise = getS3Module().then(({ S3Client }) => {
|
|
29
|
+
return new S3Client({
|
|
30
|
+
region,
|
|
31
|
+
endpoint,
|
|
32
|
+
credentials: { accessKeyId, secretAccessKey },
|
|
33
|
+
forcePathStyle: !!endpoint
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
return clientPromise.catch((error) => {
|
|
38
|
+
if (clientPromise) {
|
|
39
|
+
clientPromise = null;
|
|
40
|
+
}
|
|
41
|
+
if (s3ModulePromise && error instanceof Error) {
|
|
42
|
+
throw error;
|
|
43
|
+
}
|
|
44
|
+
throw new Error(
|
|
45
|
+
"[@btst/stack] S3 adapter requires '@aws-sdk/client-s3'. Run: npm install @aws-sdk/client-s3 @aws-sdk/s3-request-presigner"
|
|
46
|
+
);
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
async function buildSignedUrl(client, command, opts) {
|
|
50
|
+
let getSignedUrl;
|
|
51
|
+
try {
|
|
52
|
+
({ getSignedUrl } = await import('@aws-sdk/s3-request-presigner'));
|
|
53
|
+
} catch {
|
|
54
|
+
throw new Error(
|
|
55
|
+
"[@btst/stack] S3 adapter requires '@aws-sdk/s3-request-presigner'. Run: npm install @aws-sdk/client-s3 @aws-sdk/s3-request-presigner"
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
return getSignedUrl(
|
|
59
|
+
client,
|
|
60
|
+
command,
|
|
61
|
+
opts
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
return {
|
|
65
|
+
type: "s3",
|
|
66
|
+
urlPrefix: publicBaseUrl.replace(/\/$/, ""),
|
|
67
|
+
async generateUploadToken(uploadOptions) {
|
|
68
|
+
const [client, { PutObjectCommand }] = await Promise.all([
|
|
69
|
+
getClient(),
|
|
70
|
+
getS3Module()
|
|
71
|
+
]);
|
|
72
|
+
const key = uploadOptions.folderId ? `${uploadOptions.folderId}/${uploadOptions.filename}` : uploadOptions.filename;
|
|
73
|
+
const command = new PutObjectCommand({
|
|
74
|
+
Bucket: bucket,
|
|
75
|
+
Key: key,
|
|
76
|
+
ContentType: uploadOptions.mimeType,
|
|
77
|
+
ContentLength: uploadOptions.size
|
|
78
|
+
});
|
|
79
|
+
const uploadUrl = await buildSignedUrl(client, command, { expiresIn });
|
|
80
|
+
const encodedKey = key.split("/").map(encodeURIComponent).join("/");
|
|
81
|
+
const publicUrl = `${publicBaseUrl.replace(/\/$/, "")}/${encodedKey}`;
|
|
82
|
+
return {
|
|
83
|
+
type: "presigned-url",
|
|
84
|
+
payload: {
|
|
85
|
+
uploadUrl,
|
|
86
|
+
publicUrl,
|
|
87
|
+
key,
|
|
88
|
+
method: "PUT",
|
|
89
|
+
headers: { "Content-Type": uploadOptions.mimeType }
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
},
|
|
93
|
+
async delete(url) {
|
|
94
|
+
const [client, { DeleteObjectCommand }] = await Promise.all([
|
|
95
|
+
getClient(),
|
|
96
|
+
getS3Module()
|
|
97
|
+
]);
|
|
98
|
+
const base = publicBaseUrl.replace(/\/$/, "");
|
|
99
|
+
const encodedKey = url.startsWith(base) ? url.slice(base.length + 1) : url.split("/").pop() ?? url;
|
|
100
|
+
const key = decodeURIComponent(encodedKey);
|
|
101
|
+
await client.send(new DeleteObjectCommand({ Bucket: bucket, Key: key }));
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
exports.s3Adapter = s3Adapter;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { S as S3StorageAdapter } from '../../../../shared/stack.BttDsJJn.cjs';
|
|
2
|
+
|
|
3
|
+
interface S3StorageAdapterOptions {
|
|
4
|
+
/**
|
|
5
|
+
* The S3 bucket name.
|
|
6
|
+
*/
|
|
7
|
+
bucket: string;
|
|
8
|
+
/**
|
|
9
|
+
* AWS region (e.g. `"us-east-1"`).
|
|
10
|
+
*/
|
|
11
|
+
region: string;
|
|
12
|
+
/**
|
|
13
|
+
* AWS access key ID.
|
|
14
|
+
*/
|
|
15
|
+
accessKeyId: string;
|
|
16
|
+
/**
|
|
17
|
+
* AWS secret access key.
|
|
18
|
+
*/
|
|
19
|
+
secretAccessKey: string;
|
|
20
|
+
/**
|
|
21
|
+
* Custom endpoint URL for S3-compatible providers (Cloudflare R2, MinIO, etc.).
|
|
22
|
+
* @example "https://<account-id>.r2.cloudflarestorage.com"
|
|
23
|
+
*/
|
|
24
|
+
endpoint?: string;
|
|
25
|
+
/**
|
|
26
|
+
* Base URL used to construct the final public asset URL after upload.
|
|
27
|
+
* @example "https://assets.example.com" or "https://pub-<id>.r2.dev"
|
|
28
|
+
*/
|
|
29
|
+
publicBaseUrl: string;
|
|
30
|
+
/**
|
|
31
|
+
* Duration in seconds for which the presigned URL is valid.
|
|
32
|
+
* @default 300 (5 minutes)
|
|
33
|
+
*/
|
|
34
|
+
expiresIn?: number;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Create an S3-compatible presigned-URL storage adapter.
|
|
38
|
+
* The server generates a short-lived presigned PUT URL; the browser uploads
|
|
39
|
+
* the file directly to S3 (or R2 / MinIO). The server never receives file bytes.
|
|
40
|
+
*
|
|
41
|
+
* @remarks Requires `@aws-sdk/client-s3` and `@aws-sdk/s3-request-presigner`
|
|
42
|
+
* as optional peer dependencies.
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* ```ts
|
|
46
|
+
* mediaBackendPlugin({
|
|
47
|
+
* storageAdapter: s3Adapter({
|
|
48
|
+
* bucket: "my-bucket",
|
|
49
|
+
* region: "us-east-1",
|
|
50
|
+
* accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
|
|
51
|
+
* secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
|
|
52
|
+
* publicBaseUrl: "https://assets.example.com",
|
|
53
|
+
* })
|
|
54
|
+
* })
|
|
55
|
+
* ```
|
|
56
|
+
*/
|
|
57
|
+
declare function s3Adapter(options: S3StorageAdapterOptions): S3StorageAdapter;
|
|
58
|
+
|
|
59
|
+
export { s3Adapter };
|
|
60
|
+
export type { S3StorageAdapterOptions };
|