@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/README.md
CHANGED
|
@@ -37,6 +37,7 @@ Enable the features you need and keep building your product.
|
|
|
37
37
|
| **Form Builder** | Dynamic form builder with drag-and-drop editor, submissions, and validation |
|
|
38
38
|
| **UI Builder** | Visual drag-and-drop page builder with component registry and public rendering |
|
|
39
39
|
| **Kanban** | Project management with boards, columns, tasks, drag-and-drop, and priority levels |
|
|
40
|
+
| **Media** | Media library with uploads, folders, picker UI, URL registration, and reusable image inputs |
|
|
40
41
|
| **OpenAPI** | Auto-generated API documentation with interactive Scalar UI |
|
|
41
42
|
| **Route Docs** | Auto-generated client route documentation with interactive navigation |
|
|
42
43
|
| **Better Auth UI** | Beautiful shadcn/ui authentication components for better-auth |
|
|
@@ -121,8 +122,8 @@ Supports Prisma, Drizzle, MongoDB and Kysely SQL dialects.
|
|
|
121
122
|
Each plugin's UI layer is available as a [shadcn registry](https://ui.shadcn.com/docs/registry) block. Use it to **eject and fully customize** the page components while keeping all data-fetching and API logic from `@btst/stack`:
|
|
122
123
|
|
|
123
124
|
```bash
|
|
124
|
-
# Install a single plugin's UI
|
|
125
|
-
npx shadcn@latest add https://github.com/better-stack-ai/better-stack/blob/main/packages/stack/registry/btst-
|
|
125
|
+
# Install a single plugin's UI (for example, Media)
|
|
126
|
+
npx shadcn@latest add https://github.com/better-stack-ai/better-stack/blob/main/packages/stack/registry/btst-media.json
|
|
126
127
|
|
|
127
128
|
# Or install the full collection
|
|
128
129
|
npx shadcn@latest add https://github.com/better-stack-ai/better-stack/blob/main/packages/stack/registry/registry.json
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
-
import { ComponentType } from 'react';
|
|
2
|
+
import { ComponentType, MutableRefObject } from 'react';
|
|
3
3
|
|
|
4
4
|
type MarkdownContentProps = {
|
|
5
5
|
/** The markdown string to render */
|
|
@@ -23,8 +23,21 @@ interface MarkdownEditorProps {
|
|
|
23
23
|
uploadImage?: (file: File) => Promise<string>;
|
|
24
24
|
/** Placeholder text shown when the editor is empty. */
|
|
25
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;
|
|
26
39
|
}
|
|
27
|
-
declare function MarkdownEditor({ value, onChange, className, uploadImage, placeholder, }: MarkdownEditorProps): react_jsx_runtime.JSX.Element;
|
|
40
|
+
declare function MarkdownEditor({ value, onChange, className, uploadImage, placeholder, insertImageRef, openMediaPickerForImageBlock, }: MarkdownEditorProps): react_jsx_runtime.JSX.Element;
|
|
28
41
|
|
|
29
42
|
export { MarkdownContent, MarkdownEditor };
|
|
30
43
|
export type { MarkdownContentProps, MarkdownEditorProps };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
-
import { ComponentType } from 'react';
|
|
2
|
+
import { ComponentType, MutableRefObject } from 'react';
|
|
3
3
|
|
|
4
4
|
type MarkdownContentProps = {
|
|
5
5
|
/** The markdown string to render */
|
|
@@ -23,8 +23,21 @@ interface MarkdownEditorProps {
|
|
|
23
23
|
uploadImage?: (file: File) => Promise<string>;
|
|
24
24
|
/** Placeholder text shown when the editor is empty. */
|
|
25
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;
|
|
26
39
|
}
|
|
27
|
-
declare function MarkdownEditor({ value, onChange, className, uploadImage, placeholder, }: MarkdownEditorProps): react_jsx_runtime.JSX.Element;
|
|
40
|
+
declare function MarkdownEditor({ value, onChange, className, uploadImage, placeholder, insertImageRef, openMediaPickerForImageBlock, }: MarkdownEditorProps): react_jsx_runtime.JSX.Element;
|
|
28
41
|
|
|
29
42
|
export { MarkdownContent, MarkdownEditor };
|
|
30
43
|
export type { MarkdownContentProps, MarkdownEditorProps };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
-
import { ComponentType } from 'react';
|
|
2
|
+
import { ComponentType, MutableRefObject } from 'react';
|
|
3
3
|
|
|
4
4
|
type MarkdownContentProps = {
|
|
5
5
|
/** The markdown string to render */
|
|
@@ -23,8 +23,21 @@ interface MarkdownEditorProps {
|
|
|
23
23
|
uploadImage?: (file: File) => Promise<string>;
|
|
24
24
|
/** Placeholder text shown when the editor is empty. */
|
|
25
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;
|
|
26
39
|
}
|
|
27
|
-
declare function MarkdownEditor({ value, onChange, className, uploadImage, placeholder, }: MarkdownEditorProps): react_jsx_runtime.JSX.Element;
|
|
40
|
+
declare function MarkdownEditor({ value, onChange, className, uploadImage, placeholder, insertImageRef, openMediaPickerForImageBlock, }: MarkdownEditorProps): react_jsx_runtime.JSX.Element;
|
|
28
41
|
|
|
29
42
|
export { MarkdownContent, MarkdownEditor };
|
|
30
43
|
export type { MarkdownContentProps, MarkdownEditorProps };
|
|
@@ -18,8 +18,37 @@ function FeaturedImageField({
|
|
|
18
18
|
}) {
|
|
19
19
|
const fileInputRef = React.useRef(null);
|
|
20
20
|
const [isUploading, setIsUploading] = React.useState(false);
|
|
21
|
-
const {
|
|
21
|
+
const {
|
|
22
|
+
uploadImage,
|
|
23
|
+
Image,
|
|
24
|
+
localization,
|
|
25
|
+
imageInputField: ImageInput
|
|
26
|
+
} = context.usePluginOverrides(
|
|
27
|
+
"blog",
|
|
28
|
+
{ localization: index.BLOG_LOCALIZATION }
|
|
29
|
+
);
|
|
22
30
|
const ImageComponent = Image ? Image : DefaultImage;
|
|
31
|
+
if (ImageInput) {
|
|
32
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(form.FormItem, { className: "flex flex-col", children: [
|
|
33
|
+
/* @__PURE__ */ jsxRuntime.jsxs(form.FormLabel, { children: [
|
|
34
|
+
localization.BLOG_FORMS_FEATURED_IMAGE_LABEL,
|
|
35
|
+
isRequired && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-destructive", children: [
|
|
36
|
+
" ",
|
|
37
|
+
localization.BLOG_FORMS_FEATURED_IMAGE_REQUIRED_ASTERISK
|
|
38
|
+
] })
|
|
39
|
+
] }),
|
|
40
|
+
/* @__PURE__ */ jsxRuntime.jsx(form.FormControl, { children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
41
|
+
ImageInput,
|
|
42
|
+
{
|
|
43
|
+
value: value || "",
|
|
44
|
+
onChange,
|
|
45
|
+
isRequired
|
|
46
|
+
}
|
|
47
|
+
) }),
|
|
48
|
+
/* @__PURE__ */ jsxRuntime.jsx(form.FormDescription, {}),
|
|
49
|
+
/* @__PURE__ */ jsxRuntime.jsx(form.FormMessage, {})
|
|
50
|
+
] });
|
|
51
|
+
}
|
|
23
52
|
const handleImageUpload = async (event) => {
|
|
24
53
|
const file = event.target.files?.[0];
|
|
25
54
|
if (!file) return;
|
|
@@ -16,8 +16,37 @@ function FeaturedImageField({
|
|
|
16
16
|
}) {
|
|
17
17
|
const fileInputRef = useRef(null);
|
|
18
18
|
const [isUploading, setIsUploading] = useState(false);
|
|
19
|
-
const {
|
|
19
|
+
const {
|
|
20
|
+
uploadImage,
|
|
21
|
+
Image,
|
|
22
|
+
localization,
|
|
23
|
+
imageInputField: ImageInput
|
|
24
|
+
} = usePluginOverrides(
|
|
25
|
+
"blog",
|
|
26
|
+
{ localization: BLOG_LOCALIZATION }
|
|
27
|
+
);
|
|
20
28
|
const ImageComponent = Image ? Image : DefaultImage;
|
|
29
|
+
if (ImageInput) {
|
|
30
|
+
return /* @__PURE__ */ jsxs(FormItem, { className: "flex flex-col", children: [
|
|
31
|
+
/* @__PURE__ */ jsxs(FormLabel, { children: [
|
|
32
|
+
localization.BLOG_FORMS_FEATURED_IMAGE_LABEL,
|
|
33
|
+
isRequired && /* @__PURE__ */ jsxs("span", { className: "text-destructive", children: [
|
|
34
|
+
" ",
|
|
35
|
+
localization.BLOG_FORMS_FEATURED_IMAGE_REQUIRED_ASTERISK
|
|
36
|
+
] })
|
|
37
|
+
] }),
|
|
38
|
+
/* @__PURE__ */ jsx(FormControl, { children: /* @__PURE__ */ jsx(
|
|
39
|
+
ImageInput,
|
|
40
|
+
{
|
|
41
|
+
value: value || "",
|
|
42
|
+
onChange,
|
|
43
|
+
isRequired
|
|
44
|
+
}
|
|
45
|
+
) }),
|
|
46
|
+
/* @__PURE__ */ jsx(FormDescription, {}),
|
|
47
|
+
/* @__PURE__ */ jsx(FormMessage, {})
|
|
48
|
+
] });
|
|
49
|
+
}
|
|
21
50
|
const handleImageUpload = async (event) => {
|
|
22
51
|
const file = event.target.files?.[0];
|
|
23
52
|
if (!file) return;
|
|
@@ -2,22 +2,62 @@
|
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
4
|
const jsxRuntime = require('react/jsx-runtime');
|
|
5
|
+
const React = require('react');
|
|
5
6
|
const context = require('@btst/stack/context');
|
|
6
7
|
const index = require('../../localization/index.cjs');
|
|
7
8
|
const markdownEditor = require('./markdown-editor.cjs');
|
|
8
9
|
|
|
9
10
|
function MarkdownEditorWithOverrides(props) {
|
|
10
|
-
const {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
11
|
+
const {
|
|
12
|
+
uploadImage,
|
|
13
|
+
imagePicker: ImagePickerTrigger,
|
|
14
|
+
localization
|
|
15
|
+
} = context.usePluginOverrides(
|
|
16
|
+
"blog",
|
|
17
|
+
{ localization: index.BLOG_LOCALIZATION }
|
|
18
|
+
);
|
|
19
|
+
const insertImageRef = React.useRef(null);
|
|
20
|
+
const pendingInsertUrlRef = React.useRef(null);
|
|
21
|
+
const triggerContainerRef = React.useRef(null);
|
|
22
|
+
const handleSelect = React.useCallback((url) => {
|
|
23
|
+
if (pendingInsertUrlRef.current) {
|
|
24
|
+
pendingInsertUrlRef.current(url);
|
|
25
|
+
pendingInsertUrlRef.current = null;
|
|
26
|
+
} else {
|
|
27
|
+
insertImageRef.current?.(url);
|
|
19
28
|
}
|
|
29
|
+
}, []);
|
|
30
|
+
const openMediaPickerForImageBlock = React.useCallback(
|
|
31
|
+
(setUrl) => {
|
|
32
|
+
pendingInsertUrlRef.current = setUrl;
|
|
33
|
+
const btn = triggerContainerRef.current?.querySelector(
|
|
34
|
+
'[data-testid="open-media-picker"]'
|
|
35
|
+
);
|
|
36
|
+
btn?.click();
|
|
37
|
+
},
|
|
38
|
+
[]
|
|
20
39
|
);
|
|
40
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col", children: [
|
|
41
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
42
|
+
markdownEditor.MarkdownEditor,
|
|
43
|
+
{
|
|
44
|
+
...props,
|
|
45
|
+
uploadImage,
|
|
46
|
+
placeholder: localization?.BLOG_FORMS_EDITOR_PLACEHOLDER,
|
|
47
|
+
insertImageRef,
|
|
48
|
+
openMediaPickerForImageBlock: ImagePickerTrigger ? openMediaPickerForImageBlock : void 0
|
|
49
|
+
}
|
|
50
|
+
),
|
|
51
|
+
ImagePickerTrigger && /* @__PURE__ */ jsxRuntime.jsx(
|
|
52
|
+
"div",
|
|
53
|
+
{
|
|
54
|
+
ref: triggerContainerRef,
|
|
55
|
+
className: "flex justify-end mt-1",
|
|
56
|
+
"data-testid": "image-picker-trigger",
|
|
57
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(ImagePickerTrigger, { onSelect: handleSelect })
|
|
58
|
+
}
|
|
59
|
+
)
|
|
60
|
+
] });
|
|
21
61
|
}
|
|
22
62
|
|
|
23
63
|
exports.MarkdownEditorWithOverrides = MarkdownEditorWithOverrides;
|
|
@@ -1,21 +1,61 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { jsx } from 'react/jsx-runtime';
|
|
2
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
3
|
+
import { useRef, useCallback } from 'react';
|
|
3
4
|
import { usePluginOverrides } from '@btst/stack/context';
|
|
4
5
|
import { BLOG_LOCALIZATION } from '../../localization/index.mjs';
|
|
5
6
|
import { MarkdownEditor } from './markdown-editor.mjs';
|
|
6
7
|
|
|
7
8
|
function MarkdownEditorWithOverrides(props) {
|
|
8
|
-
const {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
9
|
+
const {
|
|
10
|
+
uploadImage,
|
|
11
|
+
imagePicker: ImagePickerTrigger,
|
|
12
|
+
localization
|
|
13
|
+
} = usePluginOverrides(
|
|
14
|
+
"blog",
|
|
15
|
+
{ localization: BLOG_LOCALIZATION }
|
|
16
|
+
);
|
|
17
|
+
const insertImageRef = useRef(null);
|
|
18
|
+
const pendingInsertUrlRef = useRef(null);
|
|
19
|
+
const triggerContainerRef = useRef(null);
|
|
20
|
+
const handleSelect = useCallback((url) => {
|
|
21
|
+
if (pendingInsertUrlRef.current) {
|
|
22
|
+
pendingInsertUrlRef.current(url);
|
|
23
|
+
pendingInsertUrlRef.current = null;
|
|
24
|
+
} else {
|
|
25
|
+
insertImageRef.current?.(url);
|
|
17
26
|
}
|
|
27
|
+
}, []);
|
|
28
|
+
const openMediaPickerForImageBlock = useCallback(
|
|
29
|
+
(setUrl) => {
|
|
30
|
+
pendingInsertUrlRef.current = setUrl;
|
|
31
|
+
const btn = triggerContainerRef.current?.querySelector(
|
|
32
|
+
'[data-testid="open-media-picker"]'
|
|
33
|
+
);
|
|
34
|
+
btn?.click();
|
|
35
|
+
},
|
|
36
|
+
[]
|
|
18
37
|
);
|
|
38
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex flex-col", children: [
|
|
39
|
+
/* @__PURE__ */ jsx(
|
|
40
|
+
MarkdownEditor,
|
|
41
|
+
{
|
|
42
|
+
...props,
|
|
43
|
+
uploadImage,
|
|
44
|
+
placeholder: localization?.BLOG_FORMS_EDITOR_PLACEHOLDER,
|
|
45
|
+
insertImageRef,
|
|
46
|
+
openMediaPickerForImageBlock: ImagePickerTrigger ? openMediaPickerForImageBlock : void 0
|
|
47
|
+
}
|
|
48
|
+
),
|
|
49
|
+
ImagePickerTrigger && /* @__PURE__ */ jsx(
|
|
50
|
+
"div",
|
|
51
|
+
{
|
|
52
|
+
ref: triggerContainerRef,
|
|
53
|
+
className: "flex justify-end mt-1",
|
|
54
|
+
"data-testid": "image-picker-trigger",
|
|
55
|
+
children: /* @__PURE__ */ jsx(ImagePickerTrigger, { onSelect: handleSelect })
|
|
56
|
+
}
|
|
57
|
+
)
|
|
58
|
+
] });
|
|
19
59
|
}
|
|
20
60
|
|
|
21
61
|
export { MarkdownEditorWithOverrides };
|
|
@@ -16,7 +16,9 @@ function MarkdownEditor({
|
|
|
16
16
|
onChange,
|
|
17
17
|
className,
|
|
18
18
|
uploadImage,
|
|
19
|
-
placeholder = "Write something..."
|
|
19
|
+
placeholder = "Write something...",
|
|
20
|
+
insertImageRef,
|
|
21
|
+
openMediaPickerForImageBlock
|
|
20
22
|
}) {
|
|
21
23
|
const containerRef = React.useRef(null);
|
|
22
24
|
const crepeRef = React.useRef(null);
|
|
@@ -24,12 +26,25 @@ function MarkdownEditor({
|
|
|
24
26
|
const [isReady, setIsReady] = React.useState(false);
|
|
25
27
|
const onChangeRef = React.useRef(onChange);
|
|
26
28
|
const initialValueRef = React.useRef(value ?? "");
|
|
29
|
+
const openMediaPickerRef = React.useRef(
|
|
30
|
+
openMediaPickerForImageBlock
|
|
31
|
+
);
|
|
27
32
|
const throttledOnChangeRef = React.useRef(null);
|
|
28
33
|
onChangeRef.current = onChange;
|
|
34
|
+
openMediaPickerRef.current = openMediaPickerForImageBlock;
|
|
29
35
|
React.useLayoutEffect(() => {
|
|
30
36
|
if (crepeRef.current) return;
|
|
31
37
|
const container = containerRef.current;
|
|
32
38
|
if (!container) return;
|
|
39
|
+
const hasMediaPicker = !!openMediaPickerRef.current;
|
|
40
|
+
const imageBlockConfig = {};
|
|
41
|
+
if (uploadImage) {
|
|
42
|
+
imageBlockConfig.onUpload = async (file) => uploadImage(file);
|
|
43
|
+
}
|
|
44
|
+
if (hasMediaPicker) {
|
|
45
|
+
imageBlockConfig.blockUploadPlaceholderText = "Media Picker";
|
|
46
|
+
imageBlockConfig.inlineUploadPlaceholderText = "Media Picker";
|
|
47
|
+
}
|
|
33
48
|
const crepe$1 = new crepe.Crepe({
|
|
34
49
|
root: container,
|
|
35
50
|
defaultValue: initialValueRef.current,
|
|
@@ -37,16 +52,35 @@ function MarkdownEditor({
|
|
|
37
52
|
[crepe.CrepeFeature.Placeholder]: {
|
|
38
53
|
text: placeholder
|
|
39
54
|
},
|
|
40
|
-
...
|
|
41
|
-
[crepe.CrepeFeature.ImageBlock]: {
|
|
42
|
-
onUpload: async (file) => {
|
|
43
|
-
const url = await uploadImage(file);
|
|
44
|
-
return url;
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
} : {}
|
|
55
|
+
...Object.keys(imageBlockConfig).length > 0 ? { [crepe.CrepeFeature.ImageBlock]: imageBlockConfig } : {}
|
|
48
56
|
}
|
|
49
57
|
});
|
|
58
|
+
const interceptHandler = (e) => {
|
|
59
|
+
if (!openMediaPickerRef.current) return;
|
|
60
|
+
const target = e.target;
|
|
61
|
+
const inPlaceholder = target.closest(".image-edit .placeholder");
|
|
62
|
+
if (!inPlaceholder) return;
|
|
63
|
+
if (target.matches("input")) return;
|
|
64
|
+
e.preventDefault();
|
|
65
|
+
e.stopPropagation();
|
|
66
|
+
const imageEdit = inPlaceholder.closest(".image-edit");
|
|
67
|
+
const linkInput = imageEdit?.querySelector(
|
|
68
|
+
".link-input-area"
|
|
69
|
+
);
|
|
70
|
+
openMediaPickerRef.current((url) => {
|
|
71
|
+
if (!linkInput) return;
|
|
72
|
+
const nativeSetter = Object.getOwnPropertyDescriptor(
|
|
73
|
+
HTMLInputElement.prototype,
|
|
74
|
+
"value"
|
|
75
|
+
)?.set;
|
|
76
|
+
nativeSetter?.call(linkInput, url);
|
|
77
|
+
linkInput.dispatchEvent(new Event("input", { bubbles: true }));
|
|
78
|
+
linkInput.dispatchEvent(
|
|
79
|
+
new KeyboardEvent("keydown", { key: "Enter", bubbles: true })
|
|
80
|
+
);
|
|
81
|
+
});
|
|
82
|
+
};
|
|
83
|
+
container.addEventListener("click", interceptHandler, true);
|
|
50
84
|
throttledOnChangeRef.current = utils.throttle((markdown) => {
|
|
51
85
|
if (onChangeRef.current) onChangeRef.current(markdown);
|
|
52
86
|
}, 200);
|
|
@@ -61,6 +95,7 @@ function MarkdownEditor({
|
|
|
61
95
|
});
|
|
62
96
|
crepeRef.current = crepe$1;
|
|
63
97
|
return () => {
|
|
98
|
+
container.removeEventListener("click", interceptHandler, true);
|
|
64
99
|
try {
|
|
65
100
|
isReadyRef.current = false;
|
|
66
101
|
throttledOnChangeRef.current?.cancel?.();
|
|
@@ -99,6 +134,39 @@ function MarkdownEditor({
|
|
|
99
134
|
view.dispatch(tr);
|
|
100
135
|
});
|
|
101
136
|
}, [value, isReady]);
|
|
137
|
+
React.useLayoutEffect(() => {
|
|
138
|
+
if (!insertImageRef) return;
|
|
139
|
+
insertImageRef.current = (url) => {
|
|
140
|
+
if (!crepeRef.current || !isReadyRef.current) return;
|
|
141
|
+
try {
|
|
142
|
+
const currentMarkdown = crepeRef.current.getMarkdown?.() ?? "";
|
|
143
|
+
const imageMarkdown = `
|
|
144
|
+
|
|
145
|
+

|
|
146
|
+
|
|
147
|
+
`;
|
|
148
|
+
const newMarkdown = currentMarkdown.trimEnd() + imageMarkdown;
|
|
149
|
+
crepeRef.current.editor.action((ctx) => {
|
|
150
|
+
const view = ctx.get(core.editorViewCtx);
|
|
151
|
+
const parser = ctx.get(core.parserCtx);
|
|
152
|
+
const doc = parser(newMarkdown);
|
|
153
|
+
if (!doc) return;
|
|
154
|
+
const state = view.state;
|
|
155
|
+
const tr = state.tr.replace(
|
|
156
|
+
0,
|
|
157
|
+
state.doc.content.size,
|
|
158
|
+
new model.Slice(doc.content, 0, 0)
|
|
159
|
+
);
|
|
160
|
+
view.dispatch(tr);
|
|
161
|
+
});
|
|
162
|
+
if (onChangeRef.current) onChangeRef.current(newMarkdown);
|
|
163
|
+
} catch {
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
return () => {
|
|
167
|
+
if (insertImageRef) insertImageRef.current = null;
|
|
168
|
+
};
|
|
169
|
+
}, [insertImageRef]);
|
|
102
170
|
return /* @__PURE__ */ jsxRuntime.jsx("div", { ref: containerRef, className: utils.cn("milkdown-custom", className) });
|
|
103
171
|
}
|
|
104
172
|
|
|
@@ -14,7 +14,9 @@ function MarkdownEditor({
|
|
|
14
14
|
onChange,
|
|
15
15
|
className,
|
|
16
16
|
uploadImage,
|
|
17
|
-
placeholder = "Write something..."
|
|
17
|
+
placeholder = "Write something...",
|
|
18
|
+
insertImageRef,
|
|
19
|
+
openMediaPickerForImageBlock
|
|
18
20
|
}) {
|
|
19
21
|
const containerRef = useRef(null);
|
|
20
22
|
const crepeRef = useRef(null);
|
|
@@ -22,12 +24,25 @@ function MarkdownEditor({
|
|
|
22
24
|
const [isReady, setIsReady] = useState(false);
|
|
23
25
|
const onChangeRef = useRef(onChange);
|
|
24
26
|
const initialValueRef = useRef(value ?? "");
|
|
27
|
+
const openMediaPickerRef = useRef(
|
|
28
|
+
openMediaPickerForImageBlock
|
|
29
|
+
);
|
|
25
30
|
const throttledOnChangeRef = useRef(null);
|
|
26
31
|
onChangeRef.current = onChange;
|
|
32
|
+
openMediaPickerRef.current = openMediaPickerForImageBlock;
|
|
27
33
|
useLayoutEffect(() => {
|
|
28
34
|
if (crepeRef.current) return;
|
|
29
35
|
const container = containerRef.current;
|
|
30
36
|
if (!container) return;
|
|
37
|
+
const hasMediaPicker = !!openMediaPickerRef.current;
|
|
38
|
+
const imageBlockConfig = {};
|
|
39
|
+
if (uploadImage) {
|
|
40
|
+
imageBlockConfig.onUpload = async (file) => uploadImage(file);
|
|
41
|
+
}
|
|
42
|
+
if (hasMediaPicker) {
|
|
43
|
+
imageBlockConfig.blockUploadPlaceholderText = "Media Picker";
|
|
44
|
+
imageBlockConfig.inlineUploadPlaceholderText = "Media Picker";
|
|
45
|
+
}
|
|
31
46
|
const crepe = new Crepe({
|
|
32
47
|
root: container,
|
|
33
48
|
defaultValue: initialValueRef.current,
|
|
@@ -35,16 +50,35 @@ function MarkdownEditor({
|
|
|
35
50
|
[CrepeFeature.Placeholder]: {
|
|
36
51
|
text: placeholder
|
|
37
52
|
},
|
|
38
|
-
...
|
|
39
|
-
[CrepeFeature.ImageBlock]: {
|
|
40
|
-
onUpload: async (file) => {
|
|
41
|
-
const url = await uploadImage(file);
|
|
42
|
-
return url;
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
} : {}
|
|
53
|
+
...Object.keys(imageBlockConfig).length > 0 ? { [CrepeFeature.ImageBlock]: imageBlockConfig } : {}
|
|
46
54
|
}
|
|
47
55
|
});
|
|
56
|
+
const interceptHandler = (e) => {
|
|
57
|
+
if (!openMediaPickerRef.current) return;
|
|
58
|
+
const target = e.target;
|
|
59
|
+
const inPlaceholder = target.closest(".image-edit .placeholder");
|
|
60
|
+
if (!inPlaceholder) return;
|
|
61
|
+
if (target.matches("input")) return;
|
|
62
|
+
e.preventDefault();
|
|
63
|
+
e.stopPropagation();
|
|
64
|
+
const imageEdit = inPlaceholder.closest(".image-edit");
|
|
65
|
+
const linkInput = imageEdit?.querySelector(
|
|
66
|
+
".link-input-area"
|
|
67
|
+
);
|
|
68
|
+
openMediaPickerRef.current((url) => {
|
|
69
|
+
if (!linkInput) return;
|
|
70
|
+
const nativeSetter = Object.getOwnPropertyDescriptor(
|
|
71
|
+
HTMLInputElement.prototype,
|
|
72
|
+
"value"
|
|
73
|
+
)?.set;
|
|
74
|
+
nativeSetter?.call(linkInput, url);
|
|
75
|
+
linkInput.dispatchEvent(new Event("input", { bubbles: true }));
|
|
76
|
+
linkInput.dispatchEvent(
|
|
77
|
+
new KeyboardEvent("keydown", { key: "Enter", bubbles: true })
|
|
78
|
+
);
|
|
79
|
+
});
|
|
80
|
+
};
|
|
81
|
+
container.addEventListener("click", interceptHandler, true);
|
|
48
82
|
throttledOnChangeRef.current = throttle((markdown) => {
|
|
49
83
|
if (onChangeRef.current) onChangeRef.current(markdown);
|
|
50
84
|
}, 200);
|
|
@@ -59,6 +93,7 @@ function MarkdownEditor({
|
|
|
59
93
|
});
|
|
60
94
|
crepeRef.current = crepe;
|
|
61
95
|
return () => {
|
|
96
|
+
container.removeEventListener("click", interceptHandler, true);
|
|
62
97
|
try {
|
|
63
98
|
isReadyRef.current = false;
|
|
64
99
|
throttledOnChangeRef.current?.cancel?.();
|
|
@@ -97,6 +132,39 @@ function MarkdownEditor({
|
|
|
97
132
|
view.dispatch(tr);
|
|
98
133
|
});
|
|
99
134
|
}, [value, isReady]);
|
|
135
|
+
useLayoutEffect(() => {
|
|
136
|
+
if (!insertImageRef) return;
|
|
137
|
+
insertImageRef.current = (url) => {
|
|
138
|
+
if (!crepeRef.current || !isReadyRef.current) return;
|
|
139
|
+
try {
|
|
140
|
+
const currentMarkdown = crepeRef.current.getMarkdown?.() ?? "";
|
|
141
|
+
const imageMarkdown = `
|
|
142
|
+
|
|
143
|
+

|
|
144
|
+
|
|
145
|
+
`;
|
|
146
|
+
const newMarkdown = currentMarkdown.trimEnd() + imageMarkdown;
|
|
147
|
+
crepeRef.current.editor.action((ctx) => {
|
|
148
|
+
const view = ctx.get(editorViewCtx);
|
|
149
|
+
const parser = ctx.get(parserCtx);
|
|
150
|
+
const doc = parser(newMarkdown);
|
|
151
|
+
if (!doc) return;
|
|
152
|
+
const state = view.state;
|
|
153
|
+
const tr = state.tr.replace(
|
|
154
|
+
0,
|
|
155
|
+
state.doc.content.size,
|
|
156
|
+
new Slice(doc.content, 0, 0)
|
|
157
|
+
);
|
|
158
|
+
view.dispatch(tr);
|
|
159
|
+
});
|
|
160
|
+
if (onChangeRef.current) onChangeRef.current(newMarkdown);
|
|
161
|
+
} catch {
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
return () => {
|
|
165
|
+
if (insertImageRef) insertImageRef.current = null;
|
|
166
|
+
};
|
|
167
|
+
}, [insertImageRef]);
|
|
100
168
|
return /* @__PURE__ */ jsx("div", { ref: containerRef, className: cn("milkdown-custom", className) });
|
|
101
169
|
}
|
|
102
170
|
|
|
@@ -16,25 +16,36 @@ const index = require('../../localization/index.cjs');
|
|
|
16
16
|
const fileUpload = require('./file-upload.cjs');
|
|
17
17
|
const relationField = require('./relation-field.cjs');
|
|
18
18
|
|
|
19
|
-
function buildFieldConfigFromJsonSchema(jsonSchema, uploadImage, fieldComponents) {
|
|
19
|
+
function buildFieldConfigFromJsonSchema(jsonSchema, uploadImage, fieldComponents, imagePicker, imageInputField) {
|
|
20
20
|
const baseConfig = helpers.buildFieldConfigFromJsonSchema(jsonSchema, fieldComponents);
|
|
21
21
|
const properties = jsonSchema.properties;
|
|
22
22
|
if (!properties) return baseConfig;
|
|
23
23
|
for (const [key, prop] of Object.entries(properties)) {
|
|
24
24
|
if (prop.fieldType === "file" && !fieldComponents?.["file"]) {
|
|
25
|
-
if (!uploadImage) {
|
|
25
|
+
if (!uploadImage && !imageInputField) {
|
|
26
26
|
baseConfig[key] = {
|
|
27
27
|
...baseConfig[key],
|
|
28
28
|
fieldType: () => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-md border border-destructive bg-destructive/10 p-3 text-sm text-destructive", children: [
|
|
29
29
|
"File upload requires an ",
|
|
30
30
|
/* @__PURE__ */ jsxRuntime.jsx("code", { children: "uploadImage" }),
|
|
31
|
+
" or",
|
|
32
|
+
" ",
|
|
33
|
+
/* @__PURE__ */ jsxRuntime.jsx("code", { children: "imageInputField" }),
|
|
31
34
|
" function in CMS overrides."
|
|
32
35
|
] })
|
|
33
36
|
};
|
|
34
37
|
} else {
|
|
35
38
|
baseConfig[key] = {
|
|
36
39
|
...baseConfig[key],
|
|
37
|
-
fieldType: (props) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
40
|
+
fieldType: (props) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
41
|
+
fileUpload.CMSFileUpload,
|
|
42
|
+
{
|
|
43
|
+
...props,
|
|
44
|
+
uploadImage: uploadImage ?? (() => Promise.resolve("")),
|
|
45
|
+
imageInputField,
|
|
46
|
+
imagePicker
|
|
47
|
+
}
|
|
48
|
+
)
|
|
38
49
|
};
|
|
39
50
|
}
|
|
40
51
|
}
|
|
@@ -75,6 +86,8 @@ function ContentForm({
|
|
|
75
86
|
const {
|
|
76
87
|
localization: customLocalization,
|
|
77
88
|
uploadImage,
|
|
89
|
+
imagePicker,
|
|
90
|
+
imageInputField,
|
|
78
91
|
fieldComponents
|
|
79
92
|
} = context.usePluginOverrides("cms");
|
|
80
93
|
const localization = { ...index.CMS_LOCALIZATION, ...customLocalization };
|
|
@@ -115,8 +128,14 @@ function ContentForm({
|
|
|
115
128
|
}
|
|
116
129
|
}, [jsonSchema]);
|
|
117
130
|
const fieldConfig = React.useMemo(
|
|
118
|
-
() => buildFieldConfigFromJsonSchema(
|
|
119
|
-
|
|
131
|
+
() => buildFieldConfigFromJsonSchema(
|
|
132
|
+
jsonSchema,
|
|
133
|
+
uploadImage,
|
|
134
|
+
fieldComponents,
|
|
135
|
+
imagePicker,
|
|
136
|
+
imageInputField
|
|
137
|
+
),
|
|
138
|
+
[jsonSchema, uploadImage, fieldComponents, imagePicker, imageInputField]
|
|
120
139
|
);
|
|
121
140
|
const slugSourceField = React.useMemo(
|
|
122
141
|
() => findSlugSourceField(jsonSchema),
|