@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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@btst/stack",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.9.1",
|
|
4
4
|
"description": "A composable, plugin-based library for building full-stack applications.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -414,6 +414,77 @@
|
|
|
414
414
|
}
|
|
415
415
|
},
|
|
416
416
|
"./plugins/comments/css": "./dist/plugins/comments/style.css",
|
|
417
|
+
"./plugins/media/api": {
|
|
418
|
+
"import": {
|
|
419
|
+
"types": "./dist/plugins/media/api/index.d.ts",
|
|
420
|
+
"default": "./dist/plugins/media/api/index.mjs"
|
|
421
|
+
},
|
|
422
|
+
"require": {
|
|
423
|
+
"types": "./dist/plugins/media/api/index.d.cts",
|
|
424
|
+
"default": "./dist/plugins/media/api/index.cjs"
|
|
425
|
+
}
|
|
426
|
+
},
|
|
427
|
+
"./plugins/media/api/adapters/s3": {
|
|
428
|
+
"import": {
|
|
429
|
+
"types": "./dist/plugins/media/api/adapters/s3.d.ts",
|
|
430
|
+
"default": "./dist/plugins/media/api/adapters/s3.mjs"
|
|
431
|
+
},
|
|
432
|
+
"require": {
|
|
433
|
+
"types": "./dist/plugins/media/api/adapters/s3.d.cts",
|
|
434
|
+
"default": "./dist/plugins/media/api/adapters/s3.cjs"
|
|
435
|
+
}
|
|
436
|
+
},
|
|
437
|
+
"./plugins/media/api/adapters/vercel-blob": {
|
|
438
|
+
"import": {
|
|
439
|
+
"types": "./dist/plugins/media/api/adapters/vercel-blob.d.ts",
|
|
440
|
+
"default": "./dist/plugins/media/api/adapters/vercel-blob.mjs"
|
|
441
|
+
},
|
|
442
|
+
"require": {
|
|
443
|
+
"types": "./dist/plugins/media/api/adapters/vercel-blob.d.cts",
|
|
444
|
+
"default": "./dist/plugins/media/api/adapters/vercel-blob.cjs"
|
|
445
|
+
}
|
|
446
|
+
},
|
|
447
|
+
"./plugins/media/client": {
|
|
448
|
+
"import": {
|
|
449
|
+
"types": "./dist/plugins/media/client/index.d.ts",
|
|
450
|
+
"default": "./dist/plugins/media/client/index.mjs"
|
|
451
|
+
},
|
|
452
|
+
"require": {
|
|
453
|
+
"types": "./dist/plugins/media/client/index.d.cts",
|
|
454
|
+
"default": "./dist/plugins/media/client/index.cjs"
|
|
455
|
+
}
|
|
456
|
+
},
|
|
457
|
+
"./plugins/media/client/components": {
|
|
458
|
+
"import": {
|
|
459
|
+
"types": "./dist/plugins/media/client/components/index.d.ts",
|
|
460
|
+
"default": "./dist/plugins/media/client/components/index.mjs"
|
|
461
|
+
},
|
|
462
|
+
"require": {
|
|
463
|
+
"types": "./dist/plugins/media/client/components/index.d.cts",
|
|
464
|
+
"default": "./dist/plugins/media/client/components/index.cjs"
|
|
465
|
+
}
|
|
466
|
+
},
|
|
467
|
+
"./plugins/media/client/hooks": {
|
|
468
|
+
"import": {
|
|
469
|
+
"types": "./dist/plugins/media/client/hooks/index.d.ts",
|
|
470
|
+
"default": "./dist/plugins/media/client/hooks/index.mjs"
|
|
471
|
+
},
|
|
472
|
+
"require": {
|
|
473
|
+
"types": "./dist/plugins/media/client/hooks/index.d.cts",
|
|
474
|
+
"default": "./dist/plugins/media/client/hooks/index.cjs"
|
|
475
|
+
}
|
|
476
|
+
},
|
|
477
|
+
"./plugins/media/query-keys": {
|
|
478
|
+
"import": {
|
|
479
|
+
"types": "./dist/plugins/media/query-keys.d.ts",
|
|
480
|
+
"default": "./dist/plugins/media/query-keys.mjs"
|
|
481
|
+
},
|
|
482
|
+
"require": {
|
|
483
|
+
"types": "./dist/plugins/media/query-keys.d.cts",
|
|
484
|
+
"default": "./dist/plugins/media/query-keys.cjs"
|
|
485
|
+
}
|
|
486
|
+
},
|
|
487
|
+
"./plugins/media/css": "./dist/plugins/media/client.css",
|
|
417
488
|
"./plugins/route-docs/client": {
|
|
418
489
|
"import": {
|
|
419
490
|
"types": "./dist/plugins/route-docs/client/index.d.ts",
|
|
@@ -610,6 +681,27 @@
|
|
|
610
681
|
"plugins/comments/query-keys": [
|
|
611
682
|
"./dist/plugins/comments/query-keys.d.ts"
|
|
612
683
|
],
|
|
684
|
+
"plugins/media/api": [
|
|
685
|
+
"./dist/plugins/media/api/index.d.ts"
|
|
686
|
+
],
|
|
687
|
+
"plugins/media/api/adapters/s3": [
|
|
688
|
+
"./dist/plugins/media/api/adapters/s3.d.ts"
|
|
689
|
+
],
|
|
690
|
+
"plugins/media/api/adapters/vercel-blob": [
|
|
691
|
+
"./dist/plugins/media/api/adapters/vercel-blob.d.ts"
|
|
692
|
+
],
|
|
693
|
+
"plugins/media/client": [
|
|
694
|
+
"./dist/plugins/media/client/index.d.ts"
|
|
695
|
+
],
|
|
696
|
+
"plugins/media/client/components": [
|
|
697
|
+
"./dist/plugins/media/client/components/index.d.ts"
|
|
698
|
+
],
|
|
699
|
+
"plugins/media/client/hooks": [
|
|
700
|
+
"./dist/plugins/media/client/hooks/index.d.ts"
|
|
701
|
+
],
|
|
702
|
+
"plugins/media/query-keys": [
|
|
703
|
+
"./dist/plugins/media/query-keys.d.ts"
|
|
704
|
+
],
|
|
613
705
|
"plugins/route-docs/client": [
|
|
614
706
|
"./dist/plugins/route-docs/client/index.d.ts"
|
|
615
707
|
],
|
|
@@ -646,13 +738,17 @@
|
|
|
646
738
|
},
|
|
647
739
|
"peerDependencies": {
|
|
648
740
|
"@ai-sdk/react": ">=2.0.0",
|
|
741
|
+
"@aws-sdk/client-s3": ">=3.0.0",
|
|
742
|
+
"@aws-sdk/s3-request-presigner": ">=3.0.0",
|
|
649
743
|
"@btst/yar": ">=1.2.0",
|
|
650
744
|
"@hookform/resolvers": ">=5.0.0",
|
|
651
745
|
"@radix-ui/react-dialog": ">=1.1.0",
|
|
652
746
|
"@radix-ui/react-label": ">=2.1.0",
|
|
653
747
|
"@radix-ui/react-slot": ">=1.1.0",
|
|
654
748
|
"@radix-ui/react-switch": ">=1.1.0",
|
|
749
|
+
"@tailwindcss/typography": ">=0.5.0",
|
|
655
750
|
"@tanstack/react-query": "^5.0.0",
|
|
751
|
+
"@vercel/blob": ">=0.14.0",
|
|
656
752
|
"ai": ">=5.0.0",
|
|
657
753
|
"better-call": ">=1.3.2",
|
|
658
754
|
"class-variance-authority": ">=0.7.0",
|
|
@@ -675,25 +771,38 @@
|
|
|
675
771
|
"sonner": ">=2.0.0",
|
|
676
772
|
"tailwind-merge": ">=2.6.0",
|
|
677
773
|
"tailwindcss": ">=3.0.0",
|
|
678
|
-
"@tailwindcss/typography": ">=0.5.0",
|
|
679
774
|
"zod": ">=4.2.0"
|
|
680
775
|
},
|
|
776
|
+
"peerDependenciesMeta": {
|
|
777
|
+
"@vercel/blob": {
|
|
778
|
+
"optional": true
|
|
779
|
+
},
|
|
780
|
+
"@aws-sdk/client-s3": {
|
|
781
|
+
"optional": true
|
|
782
|
+
},
|
|
783
|
+
"@aws-sdk/s3-request-presigner": {
|
|
784
|
+
"optional": true
|
|
785
|
+
}
|
|
786
|
+
},
|
|
681
787
|
"devDependencies": {
|
|
682
|
-
"tsx": "catalog:",
|
|
683
788
|
"@ai-sdk/react": "^2.0.94",
|
|
789
|
+
"@aws-sdk/client-s3": "^3.1011.0",
|
|
790
|
+
"@aws-sdk/s3-request-presigner": "^3.1011.0",
|
|
684
791
|
"@btst/adapter-memory": "2.1.1",
|
|
685
792
|
"@btst/yar": "1.2.0",
|
|
686
793
|
"@types/react": "^19.0.0",
|
|
687
794
|
"@types/slug": "^5.0.9",
|
|
795
|
+
"@vercel/blob": "^0.27.3",
|
|
688
796
|
"@workspace/ui": "workspace:*",
|
|
689
797
|
"ai": "^5.0.94",
|
|
690
798
|
"better-call": "catalog:",
|
|
799
|
+
"knip": "^5.61.2",
|
|
691
800
|
"react": "^19.1.1",
|
|
692
801
|
"react-dom": "^19.1.1",
|
|
693
802
|
"react-error-boundary": "^4.1.2",
|
|
694
|
-
"knip": "^5.61.2",
|
|
695
803
|
"rollup-plugin-preserve-directives": "0.4.0",
|
|
696
804
|
"rollup-plugin-visualizer": "^5.12.0",
|
|
805
|
+
"tsx": "catalog:",
|
|
697
806
|
"typescript": "catalog:",
|
|
698
807
|
"unbuild": "catalog:",
|
|
699
808
|
"vitest": "catalog:",
|
|
@@ -28,13 +28,44 @@ export function FeaturedImageField({
|
|
|
28
28
|
const fileInputRef = useRef<HTMLInputElement>(null);
|
|
29
29
|
const [isUploading, setIsUploading] = useState(false);
|
|
30
30
|
|
|
31
|
-
const {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
31
|
+
const {
|
|
32
|
+
uploadImage,
|
|
33
|
+
Image,
|
|
34
|
+
localization,
|
|
35
|
+
imageInputField: ImageInput,
|
|
36
|
+
} = usePluginOverrides<BlogPluginOverrides, Partial<BlogPluginOverrides>>(
|
|
37
|
+
"blog",
|
|
38
|
+
{ localization: BLOG_LOCALIZATION },
|
|
39
|
+
);
|
|
35
40
|
|
|
36
41
|
const ImageComponent = Image ? Image : DefaultImage;
|
|
37
42
|
|
|
43
|
+
// When a custom imageInput component is provided via overrides, delegate to it.
|
|
44
|
+
if (ImageInput) {
|
|
45
|
+
return (
|
|
46
|
+
<FormItem className="flex flex-col">
|
|
47
|
+
<FormLabel>
|
|
48
|
+
{localization.BLOG_FORMS_FEATURED_IMAGE_LABEL}
|
|
49
|
+
{isRequired && (
|
|
50
|
+
<span className="text-destructive">
|
|
51
|
+
{" "}
|
|
52
|
+
{localization.BLOG_FORMS_FEATURED_IMAGE_REQUIRED_ASTERISK}
|
|
53
|
+
</span>
|
|
54
|
+
)}
|
|
55
|
+
</FormLabel>
|
|
56
|
+
<FormControl>
|
|
57
|
+
<ImageInput
|
|
58
|
+
value={value || ""}
|
|
59
|
+
onChange={onChange}
|
|
60
|
+
isRequired={isRequired}
|
|
61
|
+
/>
|
|
62
|
+
</FormControl>
|
|
63
|
+
<FormDescription />
|
|
64
|
+
<FormMessage />
|
|
65
|
+
</FormItem>
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
38
69
|
const handleImageUpload = async (
|
|
39
70
|
event: React.ChangeEvent<HTMLInputElement>,
|
|
40
71
|
) => {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
"use client";
|
|
2
|
+
import { useCallback, useRef } from "react";
|
|
2
3
|
import { usePluginOverrides } from "@btst/stack/context";
|
|
3
4
|
import type { BlogPluginOverrides } from "../../overrides";
|
|
4
5
|
import { BLOG_LOCALIZATION } from "../../localization";
|
|
@@ -6,24 +7,78 @@ import { MarkdownEditor, type MarkdownEditorProps } from "./markdown-editor";
|
|
|
6
7
|
|
|
7
8
|
type MarkdownEditorWithOverridesProps = Omit<
|
|
8
9
|
MarkdownEditorProps,
|
|
9
|
-
|
|
10
|
+
| "uploadImage"
|
|
11
|
+
| "placeholder"
|
|
12
|
+
| "insertImageRef"
|
|
13
|
+
| "openMediaPickerForImageBlock"
|
|
10
14
|
>;
|
|
11
15
|
|
|
12
16
|
export function MarkdownEditorWithOverrides(
|
|
13
17
|
props: MarkdownEditorWithOverridesProps,
|
|
14
18
|
) {
|
|
15
|
-
const {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
const {
|
|
20
|
+
uploadImage,
|
|
21
|
+
imagePicker: ImagePickerTrigger,
|
|
22
|
+
localization,
|
|
23
|
+
} = usePluginOverrides<BlogPluginOverrides, Partial<BlogPluginOverrides>>(
|
|
24
|
+
"blog",
|
|
25
|
+
{ localization: BLOG_LOCALIZATION },
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
const insertImageRef = useRef<((url: string) => void) | null>(null);
|
|
29
|
+
// Holds the Crepe-image-block `setUrl` callback while the picker is open.
|
|
30
|
+
const pendingInsertUrlRef = useRef<((url: string) => void) | null>(null);
|
|
31
|
+
// Ref to the trigger wrapper so we can programmatically click the picker button.
|
|
32
|
+
const triggerContainerRef = useRef<HTMLDivElement | null>(null);
|
|
33
|
+
|
|
34
|
+
// Single onSelect handler for ImagePickerTrigger.
|
|
35
|
+
// URLs returned by the media plugin are already percent-encoded at the
|
|
36
|
+
// source (storage adapter), so no additional encoding is applied here.
|
|
37
|
+
const handleSelect = useCallback((url: string) => {
|
|
38
|
+
if (pendingInsertUrlRef.current) {
|
|
39
|
+
// Crepe image block flow: set the URL into the block's link input.
|
|
40
|
+
pendingInsertUrlRef.current(url);
|
|
41
|
+
pendingInsertUrlRef.current = null;
|
|
42
|
+
} else {
|
|
43
|
+
// Normal flow: insert image at end of markdown content.
|
|
44
|
+
insertImageRef.current?.(url);
|
|
45
|
+
}
|
|
46
|
+
}, []);
|
|
47
|
+
|
|
48
|
+
// Called by MarkdownEditor's click interceptor when the user clicks a Crepe
|
|
49
|
+
// image-block upload placeholder.
|
|
50
|
+
const openMediaPickerForImageBlock = useCallback(
|
|
51
|
+
(setUrl: (url: string) => void) => {
|
|
52
|
+
pendingInsertUrlRef.current = setUrl;
|
|
53
|
+
// Programmatically click the visible picker trigger button.
|
|
54
|
+
const btn = triggerContainerRef.current?.querySelector(
|
|
55
|
+
'[data-testid="open-media-picker"]',
|
|
56
|
+
) as HTMLButtonElement | null;
|
|
57
|
+
btn?.click();
|
|
58
|
+
},
|
|
59
|
+
[],
|
|
60
|
+
);
|
|
21
61
|
|
|
22
62
|
return (
|
|
23
|
-
<
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
63
|
+
<div className="flex flex-col">
|
|
64
|
+
<MarkdownEditor
|
|
65
|
+
{...props}
|
|
66
|
+
uploadImage={uploadImage}
|
|
67
|
+
placeholder={localization?.BLOG_FORMS_EDITOR_PLACEHOLDER}
|
|
68
|
+
insertImageRef={insertImageRef}
|
|
69
|
+
openMediaPickerForImageBlock={
|
|
70
|
+
ImagePickerTrigger ? openMediaPickerForImageBlock : undefined
|
|
71
|
+
}
|
|
72
|
+
/>
|
|
73
|
+
{ImagePickerTrigger && (
|
|
74
|
+
<div
|
|
75
|
+
ref={triggerContainerRef}
|
|
76
|
+
className="flex justify-end mt-1"
|
|
77
|
+
data-testid="image-picker-trigger"
|
|
78
|
+
>
|
|
79
|
+
<ImagePickerTrigger onSelect={handleSelect} />
|
|
80
|
+
</div>
|
|
81
|
+
)}
|
|
82
|
+
</div>
|
|
28
83
|
);
|
|
29
84
|
}
|
|
@@ -8,7 +8,12 @@ import { editorViewCtx, parserCtx } from "@milkdown/kit/core";
|
|
|
8
8
|
import { listener, listenerCtx } from "@milkdown/kit/plugin/listener";
|
|
9
9
|
import { Slice } from "@milkdown/kit/prose/model";
|
|
10
10
|
import { Selection } from "@milkdown/kit/prose/state";
|
|
11
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
useLayoutEffect,
|
|
13
|
+
useRef,
|
|
14
|
+
useState,
|
|
15
|
+
type MutableRefObject,
|
|
16
|
+
} from "react";
|
|
12
17
|
|
|
13
18
|
export interface MarkdownEditorProps {
|
|
14
19
|
value?: string;
|
|
@@ -18,6 +23,19 @@ export interface MarkdownEditorProps {
|
|
|
18
23
|
uploadImage?: (file: File) => Promise<string>;
|
|
19
24
|
/** Placeholder text shown when the editor is empty. */
|
|
20
25
|
placeholder?: string;
|
|
26
|
+
/**
|
|
27
|
+
* Optional ref that will be populated with an `insertImage(url)` function.
|
|
28
|
+
* Call `insertImageRef.current?.(url)` to programmatically insert an image.
|
|
29
|
+
* The URL must be a valid, percent-encoded URL (storage adapters guarantee this).
|
|
30
|
+
*/
|
|
31
|
+
insertImageRef?: MutableRefObject<((url: string) => void) | null>;
|
|
32
|
+
/**
|
|
33
|
+
* When provided, clicking the Crepe image block's upload area opens a media
|
|
34
|
+
* picker instead of the native file dialog. The callback receives a `setUrl`
|
|
35
|
+
* function — call it with the chosen URL to set it into the image block.
|
|
36
|
+
* The URL must be a valid, percent-encoded URL (storage adapters guarantee this).
|
|
37
|
+
*/
|
|
38
|
+
openMediaPickerForImageBlock?: (setUrl: (url: string) => void) => void;
|
|
21
39
|
}
|
|
22
40
|
|
|
23
41
|
export function MarkdownEditor({
|
|
@@ -26,6 +44,8 @@ export function MarkdownEditor({
|
|
|
26
44
|
className,
|
|
27
45
|
uploadImage,
|
|
28
46
|
placeholder = "Write something...",
|
|
47
|
+
insertImageRef,
|
|
48
|
+
openMediaPickerForImageBlock,
|
|
29
49
|
}: MarkdownEditorProps) {
|
|
30
50
|
const containerRef = useRef<HTMLDivElement | null>(null);
|
|
31
51
|
const crepeRef = useRef<Crepe | null>(null);
|
|
@@ -33,6 +53,9 @@ export function MarkdownEditor({
|
|
|
33
53
|
const [isReady, setIsReady] = useState(false);
|
|
34
54
|
const onChangeRef = useRef<typeof onChange>(onChange);
|
|
35
55
|
const initialValueRef = useRef<string>(value ?? "");
|
|
56
|
+
const openMediaPickerRef = useRef<typeof openMediaPickerForImageBlock>(
|
|
57
|
+
openMediaPickerForImageBlock,
|
|
58
|
+
);
|
|
36
59
|
type ThrottledFn = ((markdown: string) => void) & {
|
|
37
60
|
cancel?: () => void;
|
|
38
61
|
flush?: () => void;
|
|
@@ -40,12 +63,24 @@ export function MarkdownEditor({
|
|
|
40
63
|
const throttledOnChangeRef = useRef<ThrottledFn | null>(null);
|
|
41
64
|
|
|
42
65
|
onChangeRef.current = onChange;
|
|
66
|
+
openMediaPickerRef.current = openMediaPickerForImageBlock;
|
|
43
67
|
|
|
44
68
|
useLayoutEffect(() => {
|
|
45
69
|
if (crepeRef.current) return;
|
|
46
70
|
const container = containerRef.current;
|
|
47
71
|
if (!container) return;
|
|
48
72
|
|
|
73
|
+
const hasMediaPicker = !!openMediaPickerRef.current;
|
|
74
|
+
|
|
75
|
+
const imageBlockConfig: Record<string, unknown> = {};
|
|
76
|
+
if (uploadImage) {
|
|
77
|
+
imageBlockConfig.onUpload = async (file: File) => uploadImage(file);
|
|
78
|
+
}
|
|
79
|
+
if (hasMediaPicker) {
|
|
80
|
+
imageBlockConfig.blockUploadPlaceholderText = "Media Picker";
|
|
81
|
+
imageBlockConfig.inlineUploadPlaceholderText = "Media Picker";
|
|
82
|
+
}
|
|
83
|
+
|
|
49
84
|
const crepe = new Crepe({
|
|
50
85
|
root: container,
|
|
51
86
|
defaultValue: initialValueRef.current,
|
|
@@ -53,19 +88,47 @@ export function MarkdownEditor({
|
|
|
53
88
|
[CrepeFeature.Placeholder]: {
|
|
54
89
|
text: placeholder,
|
|
55
90
|
},
|
|
56
|
-
...(
|
|
57
|
-
? {
|
|
58
|
-
[CrepeFeature.ImageBlock]: {
|
|
59
|
-
onUpload: async (file: File) => {
|
|
60
|
-
const url = await uploadImage(file);
|
|
61
|
-
return url;
|
|
62
|
-
},
|
|
63
|
-
},
|
|
64
|
-
}
|
|
91
|
+
...(Object.keys(imageBlockConfig).length > 0
|
|
92
|
+
? { [CrepeFeature.ImageBlock]: imageBlockConfig }
|
|
65
93
|
: {}),
|
|
66
94
|
},
|
|
67
95
|
});
|
|
68
96
|
|
|
97
|
+
// Intercept clicks on Crepe image-block upload placeholders so that the
|
|
98
|
+
// native file dialog is suppressed and the media picker is opened instead.
|
|
99
|
+
const interceptHandler = (e: MouseEvent) => {
|
|
100
|
+
if (!openMediaPickerRef.current) return;
|
|
101
|
+
const target = e.target as Element;
|
|
102
|
+
// Only intercept clicks inside the upload placeholder area.
|
|
103
|
+
const inPlaceholder = target.closest(".image-edit .placeholder");
|
|
104
|
+
if (!inPlaceholder) return;
|
|
105
|
+
// Let the hidden file <input> itself through (shouldn't receive clicks normally).
|
|
106
|
+
if ((target as HTMLElement).matches("input")) return;
|
|
107
|
+
|
|
108
|
+
e.preventDefault();
|
|
109
|
+
e.stopPropagation();
|
|
110
|
+
|
|
111
|
+
const imageEdit = inPlaceholder.closest(".image-edit");
|
|
112
|
+
const linkInput = imageEdit?.querySelector(
|
|
113
|
+
".link-input-area",
|
|
114
|
+
) as HTMLInputElement | null;
|
|
115
|
+
|
|
116
|
+
openMediaPickerRef.current((url: string) => {
|
|
117
|
+
if (!linkInput) return;
|
|
118
|
+
// Use the native setter so Vue's reactivity picks up the change.
|
|
119
|
+
const nativeSetter = Object.getOwnPropertyDescriptor(
|
|
120
|
+
HTMLInputElement.prototype,
|
|
121
|
+
"value",
|
|
122
|
+
)?.set;
|
|
123
|
+
nativeSetter?.call(linkInput, url);
|
|
124
|
+
linkInput.dispatchEvent(new Event("input", { bubbles: true }));
|
|
125
|
+
linkInput.dispatchEvent(
|
|
126
|
+
new KeyboardEvent("keydown", { key: "Enter", bubbles: true }),
|
|
127
|
+
);
|
|
128
|
+
});
|
|
129
|
+
};
|
|
130
|
+
container.addEventListener("click", interceptHandler, true);
|
|
131
|
+
|
|
69
132
|
// Prepare throttled onChange once per editor instance
|
|
70
133
|
throttledOnChangeRef.current = throttle((markdown: string) => {
|
|
71
134
|
if (onChangeRef.current) onChangeRef.current(markdown);
|
|
@@ -86,6 +149,7 @@ export function MarkdownEditor({
|
|
|
86
149
|
crepeRef.current = crepe;
|
|
87
150
|
|
|
88
151
|
return () => {
|
|
152
|
+
container.removeEventListener("click", interceptHandler, true);
|
|
89
153
|
try {
|
|
90
154
|
isReadyRef.current = false;
|
|
91
155
|
throttledOnChangeRef.current?.cancel?.();
|
|
@@ -133,6 +197,38 @@ export function MarkdownEditor({
|
|
|
133
197
|
});
|
|
134
198
|
}, [value, isReady]);
|
|
135
199
|
|
|
200
|
+
// Expose insertImage via ref so the parent can insert images programmatically
|
|
201
|
+
useLayoutEffect(() => {
|
|
202
|
+
if (!insertImageRef) return;
|
|
203
|
+
insertImageRef.current = (url: string) => {
|
|
204
|
+
if (!crepeRef.current || !isReadyRef.current) return;
|
|
205
|
+
try {
|
|
206
|
+
const currentMarkdown = crepeRef.current.getMarkdown?.() ?? "";
|
|
207
|
+
const imageMarkdown = `\n\n\n\n`;
|
|
208
|
+
const newMarkdown = currentMarkdown.trimEnd() + imageMarkdown;
|
|
209
|
+
crepeRef.current.editor.action((ctx) => {
|
|
210
|
+
const view = ctx.get(editorViewCtx);
|
|
211
|
+
const parser = ctx.get(parserCtx);
|
|
212
|
+
const doc = parser(newMarkdown);
|
|
213
|
+
if (!doc) return;
|
|
214
|
+
const state = view.state;
|
|
215
|
+
const tr = state.tr.replace(
|
|
216
|
+
0,
|
|
217
|
+
state.doc.content.size,
|
|
218
|
+
new Slice(doc.content, 0, 0),
|
|
219
|
+
);
|
|
220
|
+
view.dispatch(tr);
|
|
221
|
+
});
|
|
222
|
+
if (onChangeRef.current) onChangeRef.current(newMarkdown);
|
|
223
|
+
} catch {
|
|
224
|
+
// Editor may not be ready yet
|
|
225
|
+
}
|
|
226
|
+
};
|
|
227
|
+
return () => {
|
|
228
|
+
if (insertImageRef) insertImageRef.current = null;
|
|
229
|
+
};
|
|
230
|
+
}, [insertImageRef]);
|
|
231
|
+
|
|
136
232
|
return (
|
|
137
233
|
<div ref={containerRef} className={cn("milkdown-custom", className)} />
|
|
138
234
|
);
|
|
@@ -2,6 +2,18 @@ import type { SerializedPost } from "../types";
|
|
|
2
2
|
import type { ComponentType, ReactNode } from "react";
|
|
3
3
|
import type { BlogLocalization } from "./localization";
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* Props for the overridable blog featured image input component.
|
|
7
|
+
*/
|
|
8
|
+
export interface BlogImageInputFieldProps {
|
|
9
|
+
/** Current image URL value */
|
|
10
|
+
value: string;
|
|
11
|
+
/** Called when the image URL changes */
|
|
12
|
+
onChange: (value: string) => void;
|
|
13
|
+
/** Whether the field is required */
|
|
14
|
+
isRequired?: boolean;
|
|
15
|
+
}
|
|
16
|
+
|
|
5
17
|
/**
|
|
6
18
|
* Context passed to lifecycle hooks
|
|
7
19
|
*/
|
|
@@ -48,9 +60,54 @@ export interface BlogPluginOverrides {
|
|
|
48
60
|
React.ImgHTMLAttributes<HTMLImageElement> & Record<string, any>
|
|
49
61
|
>;
|
|
50
62
|
/**
|
|
51
|
-
* Function used to upload
|
|
63
|
+
* Function used to upload a new image file and return its URL.
|
|
64
|
+
* This is separate from `imagePicker`, which selects an existing asset URL.
|
|
52
65
|
*/
|
|
53
66
|
uploadImage: (file: File) => Promise<string>;
|
|
67
|
+
/**
|
|
68
|
+
* Optional custom component for the featured image field.
|
|
69
|
+
*
|
|
70
|
+
* When provided it replaces the default file-upload input entirely.
|
|
71
|
+
* The component receives `value` (current URL string) and `onChange` (setter).
|
|
72
|
+
*
|
|
73
|
+
* Typical use case: render a preview when a value is set, and a media-picker
|
|
74
|
+
* trigger when no value is set.
|
|
75
|
+
*
|
|
76
|
+
* @example
|
|
77
|
+
* ```tsx
|
|
78
|
+
* imageInputField: ({ value, onChange }) =>
|
|
79
|
+
* value ? (
|
|
80
|
+
* <div>
|
|
81
|
+
* <img src={value} alt="Preview" />
|
|
82
|
+
* <MediaPicker trigger={<button>Change</button>} accept={["image/*"]}
|
|
83
|
+
* onSelect={(assets) => onChange(assets[0].url)} />
|
|
84
|
+
* </div>
|
|
85
|
+
* ) : (
|
|
86
|
+
* <MediaPicker trigger={<button>Browse media</button>} accept={["image/*"]}
|
|
87
|
+
* onSelect={(assets) => onChange(assets[0].url)} />
|
|
88
|
+
* )
|
|
89
|
+
* ```
|
|
90
|
+
*/
|
|
91
|
+
imageInputField?: ComponentType<BlogImageInputFieldProps>;
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Optional trigger component for a media picker.
|
|
95
|
+
* When provided, it is rendered adjacent to the Markdown editor and allows
|
|
96
|
+
* users to browse and select previously uploaded assets.
|
|
97
|
+
* Receives `onSelect(url)` — insert the chosen URL into the editor.
|
|
98
|
+
*
|
|
99
|
+
* @example
|
|
100
|
+
* ```tsx
|
|
101
|
+
* imagePicker: ({ onSelect }) => (
|
|
102
|
+
* <MediaPicker
|
|
103
|
+
* trigger={<Button size="sm" variant="outline">Browse media</Button>}
|
|
104
|
+
* accept={["image/*"]}
|
|
105
|
+
* onSelect={(assets) => onSelect(assets[0].url)}
|
|
106
|
+
* />
|
|
107
|
+
* )
|
|
108
|
+
* ```
|
|
109
|
+
*/
|
|
110
|
+
imagePicker?: ComponentType<{ onSelect: (url: string) => void }>;
|
|
54
111
|
/**
|
|
55
112
|
* Localization object for the blog plugin
|
|
56
113
|
*/
|
|
@@ -56,6 +56,12 @@ function buildFieldConfigFromJsonSchema(
|
|
|
56
56
|
string,
|
|
57
57
|
React.ComponentType<AutoFormInputComponentProps>
|
|
58
58
|
>,
|
|
59
|
+
imagePicker?: React.ComponentType<{ onSelect: (url: string) => void }>,
|
|
60
|
+
imageInputField?: React.ComponentType<{
|
|
61
|
+
value: string;
|
|
62
|
+
onChange: (value: string) => void;
|
|
63
|
+
isRequired?: boolean;
|
|
64
|
+
}>,
|
|
59
65
|
): FieldConfig<Record<string, unknown>> {
|
|
60
66
|
// Get base config from shared utility (handles fieldType from JSON Schema)
|
|
61
67
|
const baseConfig = buildFieldConfigBase(jsonSchema, fieldComponents);
|
|
@@ -73,14 +79,14 @@ function buildFieldConfigFromJsonSchema(
|
|
|
73
79
|
// Handle "file" fieldType when there's NO custom component for "file"
|
|
74
80
|
if (prop.fieldType === "file" && !fieldComponents?.["file"]) {
|
|
75
81
|
// Use CMSFileUpload as the default file component
|
|
76
|
-
if (!uploadImage) {
|
|
77
|
-
// Show a clear error message if uploadImage is
|
|
82
|
+
if (!uploadImage && !imageInputField) {
|
|
83
|
+
// Show a clear error message if neither uploadImage nor imageInputField is provided
|
|
78
84
|
baseConfig[key] = {
|
|
79
85
|
...baseConfig[key],
|
|
80
86
|
fieldType: () => (
|
|
81
87
|
<div className="rounded-md border border-destructive bg-destructive/10 p-3 text-sm text-destructive">
|
|
82
|
-
File upload requires an <code>uploadImage</code>
|
|
83
|
-
overrides.
|
|
88
|
+
File upload requires an <code>uploadImage</code> or{" "}
|
|
89
|
+
<code>imageInputField</code> function in CMS overrides.
|
|
84
90
|
</div>
|
|
85
91
|
),
|
|
86
92
|
};
|
|
@@ -88,7 +94,12 @@ function buildFieldConfigFromJsonSchema(
|
|
|
88
94
|
baseConfig[key] = {
|
|
89
95
|
...baseConfig[key],
|
|
90
96
|
fieldType: (props: AutoFormInputComponentProps) => (
|
|
91
|
-
<CMSFileUpload
|
|
97
|
+
<CMSFileUpload
|
|
98
|
+
{...props}
|
|
99
|
+
uploadImage={uploadImage ?? (() => Promise.resolve(""))}
|
|
100
|
+
imageInputField={imageInputField}
|
|
101
|
+
imagePicker={imagePicker}
|
|
102
|
+
/>
|
|
92
103
|
),
|
|
93
104
|
};
|
|
94
105
|
}
|
|
@@ -151,6 +162,8 @@ export function ContentForm({
|
|
|
151
162
|
const {
|
|
152
163
|
localization: customLocalization,
|
|
153
164
|
uploadImage,
|
|
165
|
+
imagePicker,
|
|
166
|
+
imageInputField,
|
|
154
167
|
fieldComponents,
|
|
155
168
|
} = usePluginOverrides<CMSPluginOverrides>("cms");
|
|
156
169
|
const localization = { ...CMS_LOCALIZATION, ...customLocalization };
|
|
@@ -214,8 +227,14 @@ export function ContentForm({
|
|
|
214
227
|
// Build field config for AutoForm (fieldType is now embedded in jsonSchema)
|
|
215
228
|
const fieldConfig = useMemo(
|
|
216
229
|
() =>
|
|
217
|
-
buildFieldConfigFromJsonSchema(
|
|
218
|
-
|
|
230
|
+
buildFieldConfigFromJsonSchema(
|
|
231
|
+
jsonSchema,
|
|
232
|
+
uploadImage,
|
|
233
|
+
fieldComponents,
|
|
234
|
+
imagePicker,
|
|
235
|
+
imageInputField,
|
|
236
|
+
),
|
|
237
|
+
[jsonSchema, uploadImage, fieldComponents, imagePicker, imageInputField],
|
|
219
238
|
);
|
|
220
239
|
|
|
221
240
|
// Find the field to use for slug auto-generation
|