@airoom/nextmin-react 1.4.0 → 1.4.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.
Files changed (40) hide show
  1. package/dist/components/SchemaForm.js +15 -2
  2. package/dist/components/SchemaSuggestionList.d.ts +7 -0
  3. package/dist/components/SchemaSuggestionList.js +43 -0
  4. package/dist/components/editor/TiptapEditor.d.ts +10 -0
  5. package/dist/components/editor/TiptapEditor.js +228 -0
  6. package/dist/components/editor/Toolbar.d.ts +6 -0
  7. package/dist/components/editor/Toolbar.js +99 -0
  8. package/dist/components/editor/components/CommandList.d.ts +7 -0
  9. package/dist/components/editor/components/CommandList.js +47 -0
  10. package/dist/components/editor/components/ImageBubbleMenu.d.ts +6 -0
  11. package/dist/components/editor/components/ImageBubbleMenu.js +15 -0
  12. package/dist/components/editor/components/ImageComponent.d.ts +3 -0
  13. package/dist/components/editor/components/ImageComponent.js +45 -0
  14. package/dist/components/editor/components/SchemaInsertionModal.d.ts +14 -0
  15. package/dist/components/editor/components/SchemaInsertionModal.js +38 -0
  16. package/dist/components/editor/components/TableBubbleMenu.d.ts +6 -0
  17. package/dist/components/editor/components/TableBubbleMenu.js +8 -0
  18. package/dist/components/editor/extensions/Container.d.ts +2 -0
  19. package/dist/components/editor/extensions/Container.js +51 -0
  20. package/dist/components/editor/extensions/Grid.d.ts +3 -0
  21. package/dist/components/editor/extensions/Grid.js +89 -0
  22. package/dist/components/editor/extensions/Layout.d.ts +3 -0
  23. package/dist/components/editor/extensions/Layout.js +116 -0
  24. package/dist/components/editor/extensions/ResizableImage.d.ts +1 -0
  25. package/dist/components/editor/extensions/ResizableImage.js +52 -0
  26. package/dist/components/editor/extensions/SlashCommand.d.ts +15 -0
  27. package/dist/components/editor/extensions/SlashCommand.js +161 -0
  28. package/dist/components/editor/utils/upload.d.ts +1 -0
  29. package/dist/components/editor/utils/upload.js +49 -0
  30. package/dist/editor.css +460 -0
  31. package/dist/lib/upload.d.ts +1 -0
  32. package/dist/lib/upload.js +53 -0
  33. package/dist/lib/utils.d.ts +2 -0
  34. package/dist/lib/utils.js +5 -0
  35. package/dist/nextmin.css +1 -1
  36. package/dist/router/NextMinRouter.d.ts +1 -0
  37. package/dist/router/NextMinRouter.js +1 -0
  38. package/dist/views/list/useListData.js +4 -0
  39. package/package.json +34 -8
  40. package/tsconfig.json +8 -3
@@ -0,0 +1,38 @@
1
+ 'use client';
2
+ import { jsxs as _jsxs, jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { useState } from 'react';
4
+ import { Modal, ModalContent, ModalHeader, ModalBody, ModalFooter, Button,
5
+ // RadioGroup, // Removed
6
+ // Radio, // Removed
7
+ Input, } from '@heroui/react';
8
+ import { RefMultiSelect } from '../../RefMultiSelect';
9
+ import { cn } from '../../../lib/utils'; // Assuming cn utility is available
10
+ export const SchemaInsertionModal = ({ isOpen, onClose, onInsert, schema, }) => {
11
+ const [viewType, setViewType] = useState('grid');
12
+ const [template, setTemplate] = useState('');
13
+ const [urlPattern, setUrlPattern] = useState('{slug}_{id}');
14
+ const [selectedIds, setSelectedIds] = useState([]);
15
+ // const [limit, setLimit] = useState('6'); // Removed limit
16
+ const handleInsert = () => {
17
+ onInsert({ viewType, template, ids: selectedIds, urlPattern });
18
+ onClose();
19
+ // Reset state
20
+ setTimeout(() => {
21
+ setViewType('grid');
22
+ setTemplate('');
23
+ setUrlPattern('{slug}_{id}');
24
+ setSelectedIds([]);
25
+ }, 200);
26
+ };
27
+ if (!schema)
28
+ return null;
29
+ return (_jsx(Modal, { isOpen: isOpen, onClose: onClose, size: "2xl", classNames: {
30
+ base: "bg-white dark:bg-zinc-950 border border-gray-200 dark:border-gray-800",
31
+ header: "border-b border-gray-200 dark:border-gray-800",
32
+ footer: "border-t border-gray-200 dark:border-gray-800",
33
+ }, children: _jsx(ModalContent, { children: _jsxs(_Fragment, { children: [_jsxs(ModalHeader, { children: ["Insert ", schema.modelName] }), _jsxs(ModalBody, { className: "py-6 flex flex-col gap-6", children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx("label", { className: "text-sm font-medium", children: "Select Items" }), _jsx(RefMultiSelect, { name: "items", label: "Search & Select", refModel: schema.modelName, value: selectedIds, onChange: (ids) => setSelectedIds(ids), pageSize: 20 }), _jsx("p", { className: "text-xs text-gray-400", children: "Leave empty to fetch latest items automatically." })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx("label", { className: "text-sm font-medium", children: "View Type" }), _jsxs("div", { className: "flex gap-4", children: [_jsx("button", { onClick: () => setViewType('grid'), className: cn("px-4 py-2 rounded-lg border text-sm font-medium transition-colors", viewType === 'grid'
34
+ ? "bg-primary-50 border-primary-500 text-primary-700 dark:bg-primary-900/20 dark:text-primary-400"
35
+ : "bg-transparent border-gray-200 dark:border-gray-800 hover:bg-gray-50"), children: "Grid (Pills)" }), _jsx("button", { onClick: () => setViewType('list'), className: cn("px-4 py-2 rounded-lg border text-sm font-medium transition-colors", viewType === 'list'
36
+ ? "bg-primary-50 border-primary-500 text-primary-700 dark:bg-primary-900/20 dark:text-primary-400"
37
+ : "bg-transparent border-gray-200 dark:border-gray-800 hover:bg-gray-50"), children: "List (Rows)" })] })] }), _jsx(Input, { label: "Template String", placeholder: "e.g. {name} - {designation}", value: template, onChange: (e) => setTemplate(e.target.value), description: "Use {fieldName} to insert dynamic data", variant: "bordered", labelPlacement: "outside" }), _jsx(Input, { label: "URL Suffix", placeholder: "{slug}_{id}", value: urlPattern, onChange: (e) => setUrlPattern(e.target.value), description: `Appended to ${process.env.NEXT_PUBLIC_FRONTEND_URL || 'BASE_URL'}/...`, variant: "bordered", labelPlacement: "outside" })] }), _jsxs(ModalFooter, { children: [_jsx(Button, { variant: "flat", onPress: onClose, children: "Cancel" }), _jsx(Button, { color: "primary", onPress: handleInsert, children: "Insert" })] })] }) }) }));
38
+ };
@@ -0,0 +1,6 @@
1
+ import { Editor } from '@tiptap/react';
2
+ interface TableBubbleMenuProps {
3
+ editor: Editor | null;
4
+ }
5
+ export declare const TableBubbleMenu: ({ editor }: TableBubbleMenuProps) => import("react/jsx-runtime").JSX.Element | null;
6
+ export {};
@@ -0,0 +1,8 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { BubbleMenu } from '@tiptap/react/menus';
3
+ import { Columns, Rows, Trash2 } from 'lucide-react';
4
+ export const TableBubbleMenu = ({ editor }) => {
5
+ if (!editor)
6
+ return null;
7
+ return (_jsxs(BubbleMenu, { editor: editor, shouldShow: ({ editor }) => editor.isActive('table'), className: "flex items-center gap-1 bg-white dark:bg-zinc-800 border border-gray-200 dark:border-gray-700 shadow-lg p-1 rounded-lg", children: [_jsxs("button", { type: "button", onClick: () => editor.chain().focus().addColumnBefore().run(), className: "p-1.5 rounded hover:bg-gray-100 dark:hover:bg-gray-700 text-gray-700 dark:text-gray-200", title: "Add Column Before", children: [_jsx(Columns, { size: 14, className: "rotate-180" }), "+"] }), _jsxs("button", { type: "button", onClick: () => editor.chain().focus().addColumnAfter().run(), className: "p-1.5 rounded hover:bg-gray-100 dark:hover:bg-gray-700 text-gray-700 dark:text-gray-200", title: "Add Column After", children: [_jsx(Columns, { size: 14 }), "+"] }), _jsxs("button", { type: "button", onClick: () => editor.chain().focus().deleteColumn().run(), className: "p-1.5 rounded hover:bg-gray-100 dark:hover:bg-gray-700 text-red-500", title: "Delete Column", children: [_jsx(Columns, { size: 14 }), "-"] }), _jsx("div", { className: "w-[1px] h-4 bg-gray-300 dark:bg-gray-600 mx-1" }), _jsxs("button", { type: "button", onClick: () => editor.chain().focus().addRowBefore().run(), className: "p-1.5 rounded hover:bg-gray-100 dark:hover:bg-gray-700 text-gray-700 dark:text-gray-200", title: "Add Row Before", children: [_jsx(Rows, { size: 14, className: "rotate-180" }), "+"] }), _jsxs("button", { type: "button", onClick: () => editor.chain().focus().addRowAfter().run(), className: "p-1.5 rounded hover:bg-gray-100 dark:hover:bg-gray-700 text-gray-700 dark:text-gray-200", title: "Add Row After", children: [_jsx(Rows, { size: 14 }), "+"] }), _jsxs("button", { type: "button", onClick: () => editor.chain().focus().deleteRow().run(), className: "p-1.5 rounded hover:bg-gray-100 dark:hover:bg-gray-700 text-red-500", title: "Delete Row", children: [_jsx(Rows, { size: 14 }), "-"] }), _jsx("div", { className: "w-[1px] h-4 bg-gray-300 dark:bg-gray-600 mx-1" }), _jsx("button", { type: "button", onClick: () => editor.chain().focus().deleteTable().run(), className: "p-1.5 rounded hover:bg-gray-100 dark:hover:bg-gray-700 text-red-500", title: "Delete Table", children: _jsx(Trash2, { size: 16 }) })] }));
8
+ };
@@ -0,0 +1,2 @@
1
+ import { Node } from '@tiptap/core';
2
+ export declare const Container: Node<any, any>;
@@ -0,0 +1,51 @@
1
+ import { Node, mergeAttributes } from '@tiptap/core';
2
+ export const Container = Node.create({
3
+ name: 'container',
4
+ group: 'block',
5
+ content: 'block+', // Can contain paragraphs, lists, etc.
6
+ defining: true, // Prevents node from being replaced when content is pasted
7
+ isolating: false, // Allow backspace to delete/merge
8
+ addAttributes() {
9
+ return {
10
+ backgroundColor: {
11
+ default: null,
12
+ parseHTML: element => element.style.backgroundColor,
13
+ renderHTML: attributes => {
14
+ if (!attributes.backgroundColor)
15
+ return {};
16
+ return {
17
+ style: `background-color: ${attributes.backgroundColor}`,
18
+ };
19
+ },
20
+ },
21
+ borderRadius: {
22
+ default: null,
23
+ parseHTML: element => element.style.borderRadius,
24
+ renderHTML: attributes => {
25
+ if (!attributes.borderRadius)
26
+ return {};
27
+ return {
28
+ style: `border-radius: ${attributes.borderRadius}`,
29
+ };
30
+ },
31
+ },
32
+ };
33
+ },
34
+ parseHTML() {
35
+ return [
36
+ {
37
+ tag: 'div[data-type="container"]',
38
+ },
39
+ ];
40
+ },
41
+ renderHTML({ HTMLAttributes }) {
42
+ return [
43
+ 'div',
44
+ mergeAttributes(HTMLAttributes, {
45
+ 'data-type': 'container',
46
+ class: 'nm-editor-container',
47
+ }),
48
+ 0,
49
+ ];
50
+ },
51
+ });
@@ -0,0 +1,3 @@
1
+ import { Node } from '@tiptap/core';
2
+ export declare const GridContainer: Node<any, any>;
3
+ export declare const GridItem: Node<any, any>;
@@ -0,0 +1,89 @@
1
+ import { Node } from '@tiptap/core';
2
+ export const GridContainer = Node.create({
3
+ name: 'gridContainer',
4
+ group: 'block',
5
+ content: 'gridItem+', // Contains only grid items
6
+ defining: true,
7
+ isolating: true,
8
+ parseHTML() {
9
+ return [
10
+ { tag: 'div[data-type="grid-container"]', priority: 51 },
11
+ { tag: 'div.nm-editor-grid', priority: 51 },
12
+ ];
13
+ },
14
+ addAttributes() {
15
+ return {
16
+ backgroundColor: {
17
+ default: null,
18
+ parseHTML: element => element.style.getPropertyValue('--nm-grid-card-bg'),
19
+ renderHTML: attributes => {
20
+ if (!attributes.backgroundColor)
21
+ return {};
22
+ return {
23
+ style: `--nm-grid-card-bg: ${attributes.backgroundColor}`,
24
+ };
25
+ },
26
+ },
27
+ borderRadius: {
28
+ default: null,
29
+ parseHTML: element => element.style.getPropertyValue('--nm-grid-radius')?.replace(/["']/g, ''), // parse from var if possible, or just ignore since it's logical
30
+ renderHTML: attributes => {
31
+ if (!attributes.borderRadius)
32
+ return {};
33
+ return {
34
+ style: `--nm-grid-radius: ${attributes.borderRadius}`,
35
+ };
36
+ },
37
+ },
38
+ };
39
+ },
40
+ renderHTML({ HTMLAttributes }) {
41
+ return [
42
+ 'div',
43
+ {
44
+ 'data-type': 'grid-container',
45
+ ...HTMLAttributes,
46
+ class: 'nm-editor-grid',
47
+ },
48
+ 0,
49
+ ];
50
+ },
51
+ });
52
+ export const GridItem = Node.create({
53
+ name: 'gridItem',
54
+ group: 'block',
55
+ content: 'block+', // Allow blocks (paragraphs) prevents flattening
56
+ defining: true,
57
+ isolating: true,
58
+ addAttributes() {
59
+ return {
60
+ href: {
61
+ default: null,
62
+ parseHTML: element => element.getAttribute('href'),
63
+ },
64
+ };
65
+ },
66
+ parseHTML() {
67
+ return [
68
+ { tag: 'div[data-type="grid-item"]', priority: 60 },
69
+ { tag: 'div.nm-editor-grid-card', priority: 60 },
70
+ { tag: 'a[data-type="grid-item"]', priority: 60 },
71
+ { tag: 'a.nm-editor-grid-card', priority: 60 },
72
+ ];
73
+ },
74
+ renderHTML({ HTMLAttributes }) {
75
+ const { href, class: _c, className: _cn, ...attributesWithoutHref } = HTMLAttributes;
76
+ const hasHref = !!href;
77
+ const Tag = hasHref ? 'a' : 'div';
78
+ return [
79
+ Tag,
80
+ {
81
+ 'data-type': 'grid-item',
82
+ ...attributesWithoutHref,
83
+ ...(hasHref ? { href } : {}),
84
+ class: 'nm-editor-grid-card',
85
+ },
86
+ 0,
87
+ ];
88
+ },
89
+ });
@@ -0,0 +1,3 @@
1
+ import { Node } from '@tiptap/core';
2
+ export declare const LayoutRow: Node<any, any>;
3
+ export declare const LayoutColumn: Node<any, any>;
@@ -0,0 +1,116 @@
1
+ import { Node, mergeAttributes } from '@tiptap/core';
2
+ export const LayoutRow = Node.create({
3
+ name: 'layoutRow',
4
+ group: 'block',
5
+ content: 'layoutColumn+', // Must contain one or more columns
6
+ defining: true,
7
+ isolating: true,
8
+ addAttributes() {
9
+ return {
10
+ cols: {
11
+ default: 2,
12
+ parseHTML: element => element.getAttribute('data-cols'),
13
+ renderHTML: attributes => ({
14
+ 'data-cols': attributes.cols,
15
+ }),
16
+ },
17
+ backgroundColor: {
18
+ default: null,
19
+ parseHTML: element => element.style.backgroundColor,
20
+ renderHTML: attributes => {
21
+ if (!attributes.backgroundColor)
22
+ return {};
23
+ return {
24
+ style: `background-color: ${attributes.backgroundColor}`,
25
+ };
26
+ },
27
+ },
28
+ borderRadius: {
29
+ default: null,
30
+ parseHTML: element => element.style.borderRadius,
31
+ renderHTML: attributes => {
32
+ if (!attributes.borderRadius)
33
+ return {};
34
+ return {
35
+ style: `border-radius: ${attributes.borderRadius}`,
36
+ };
37
+ },
38
+ },
39
+ };
40
+ },
41
+ parseHTML() {
42
+ return [
43
+ {
44
+ tag: 'div[data-type="layout-row"]',
45
+ },
46
+ ];
47
+ },
48
+ renderHTML({ HTMLAttributes }) {
49
+ const cols = HTMLAttributes.cols || 2;
50
+ let gridClass = 'nm-editor-row-2';
51
+ if (cols == 3) {
52
+ gridClass = 'nm-editor-row-3';
53
+ }
54
+ else if (cols == 1) {
55
+ gridClass = 'nm-editor-row-1';
56
+ }
57
+ return [
58
+ 'div',
59
+ mergeAttributes(HTMLAttributes, {
60
+ 'data-type': 'layout-row',
61
+ class: gridClass,
62
+ }),
63
+ 0,
64
+ ];
65
+ },
66
+ });
67
+ export const LayoutColumn = Node.create({
68
+ name: 'layoutColumn',
69
+ group: 'block',
70
+ content: 'block+', // Can contain paragraphs, lists, images, etc.
71
+ defining: true,
72
+ isolating: true,
73
+ addAttributes() {
74
+ return {
75
+ backgroundColor: {
76
+ default: null,
77
+ parseHTML: element => element.style.backgroundColor,
78
+ renderHTML: attributes => {
79
+ if (!attributes.backgroundColor)
80
+ return {};
81
+ return {
82
+ style: `background-color: ${attributes.backgroundColor}`,
83
+ };
84
+ },
85
+ },
86
+ borderRadius: {
87
+ default: null,
88
+ parseHTML: element => element.style.borderRadius,
89
+ renderHTML: attributes => {
90
+ if (!attributes.borderRadius)
91
+ return {};
92
+ return {
93
+ style: `border-radius: ${attributes.borderRadius}`,
94
+ };
95
+ },
96
+ },
97
+ };
98
+ },
99
+ parseHTML() {
100
+ return [
101
+ {
102
+ tag: 'div[data-type="layout-column"]',
103
+ },
104
+ ];
105
+ },
106
+ renderHTML({ HTMLAttributes }) {
107
+ return [
108
+ 'div',
109
+ mergeAttributes(HTMLAttributes, {
110
+ 'data-type': 'layout-column',
111
+ class: 'nm-editor-col',
112
+ }),
113
+ 0,
114
+ ];
115
+ },
116
+ });
@@ -0,0 +1 @@
1
+ export declare const ResizableImage: import("@tiptap/core").Node<import("@tiptap/extension-image").ImageOptions, any>;
@@ -0,0 +1,52 @@
1
+ import Image from '@tiptap/extension-image';
2
+ import { ReactNodeViewRenderer } from '@tiptap/react';
3
+ import { ImageComponent } from '../components/ImageComponent';
4
+ import { mergeAttributes } from '@tiptap/core';
5
+ export const ResizableImage = Image.extend({
6
+ name: 'image',
7
+ group: 'block',
8
+ inline: false,
9
+ draggable: true,
10
+ addAttributes() {
11
+ return {
12
+ ...this.parent?.(),
13
+ width: {
14
+ default: null,
15
+ renderHTML: (attributes) => {
16
+ if (!attributes.width)
17
+ return {};
18
+ return { width: attributes.width };
19
+ }
20
+ },
21
+ height: {
22
+ default: null,
23
+ renderHTML: (attributes) => {
24
+ if (!attributes.height)
25
+ return {};
26
+ return { height: attributes.height };
27
+ }
28
+ },
29
+ src: {
30
+ default: '',
31
+ },
32
+ alt: {
33
+ default: '',
34
+ },
35
+ objectFit: {
36
+ default: 'contain', // cover, contain, fill
37
+ renderHTML: (attributes) => {
38
+ if (!attributes.objectFit)
39
+ return {};
40
+ return { style: `object-fit: ${attributes.objectFit}` };
41
+ }
42
+ },
43
+ };
44
+ },
45
+ addNodeView() {
46
+ return ReactNodeViewRenderer(ImageComponent);
47
+ },
48
+ // Custom renderHTML to ensure attributes are correct in output
49
+ renderHTML({ HTMLAttributes }) {
50
+ return ['img', mergeAttributes(this.options.HTMLAttributes, HTMLAttributes)];
51
+ },
52
+ });
@@ -0,0 +1,15 @@
1
+ import { Extension } from '@tiptap/core';
2
+ import { SchemaDef } from '../../../lib/types';
3
+ export declare const SlashCommand: Extension<any, any>;
4
+ export declare const getSuggestionOptions: (items: SchemaDef[], onSelect?: (item: SchemaDef) => void) => {
5
+ items: ({ query }: {
6
+ query: string;
7
+ }) => SchemaDef[];
8
+ command: ({ editor, range }: any) => void;
9
+ render: () => {
10
+ onStart: (props: any) => void;
11
+ onUpdate(props: any): void;
12
+ onKeyDown(props: any): any;
13
+ onExit(): void;
14
+ };
15
+ };
@@ -0,0 +1,161 @@
1
+ import { Extension } from '@tiptap/core';
2
+ import Suggestion from '@tiptap/suggestion';
3
+ import { ReactRenderer } from '@tiptap/react';
4
+ import { CommandList } from '../components/CommandList';
5
+ import { List, LayoutTemplate, LayoutPanelLeft } from 'lucide-react'; // Assuming these icons are available
6
+ export const SlashCommand = Extension.create({
7
+ name: 'slashCommand',
8
+ addOptions() {
9
+ return {
10
+ suggestion: {
11
+ char: '/',
12
+ command: ({ editor, range }) => {
13
+ // Just delete the range (the "/") to clean up
14
+ editor.chain().focus().deleteRange(range).run();
15
+ },
16
+ items: ({ query }) => {
17
+ const allCommands = [
18
+ {
19
+ title: 'Bullet List',
20
+ description: 'Create a simple bullet list.',
21
+ icon: List,
22
+ command: ({ editor, range }) => {
23
+ editor.chain().focus().deleteRange(range).toggleBulletList().run();
24
+ },
25
+ },
26
+ {
27
+ title: '2 Columns',
28
+ description: 'Insert 2 responsive columns',
29
+ icon: LayoutTemplate,
30
+ command: ({ editor, range }) => {
31
+ editor
32
+ .chain()
33
+ .focus()
34
+ .deleteRange(range)
35
+ .insertContent('<p></p>') // Ensure separation
36
+ .insertContent('<div data-type="layout-row" data-cols="2"><div data-type="layout-column"><p></p></div><div data-type="layout-column"><p></p></div></div>')
37
+ .run();
38
+ },
39
+ },
40
+ {
41
+ title: '3 Columns',
42
+ description: 'Insert 3 responsive columns',
43
+ icon: LayoutPanelLeft,
44
+ command: ({ editor, range }) => {
45
+ editor
46
+ .chain()
47
+ .focus()
48
+ .deleteRange(range)
49
+ .insertContent('<p></p>') // Ensure separation
50
+ .insertContent('<div data-type="layout-row" data-cols="3"><div data-type="layout-column"><p></p></div><div data-type="layout-column"><p></p></div><div data-type="layout-column"><p></p></div></div>')
51
+ .run();
52
+ },
53
+ },
54
+ // Add other static commands here if any
55
+ ];
56
+ // Filter commands based on query
57
+ return allCommands
58
+ .filter((item) => item.title.toLowerCase().startsWith(query.toLowerCase()))
59
+ .slice(0, 10);
60
+ },
61
+ },
62
+ };
63
+ },
64
+ addProseMirrorPlugins() {
65
+ return [
66
+ Suggestion({
67
+ editor: this.editor,
68
+ ...this.options.suggestion,
69
+ }),
70
+ ];
71
+ },
72
+ });
73
+ export const getSuggestionOptions = (items, onSelect) => ({
74
+ items: ({ query }) => {
75
+ return items
76
+ .filter((item) => item.modelName.toLowerCase().startsWith(query.toLowerCase()))
77
+ .slice(0, 10);
78
+ },
79
+ command: ({ editor, range }) => {
80
+ // For schema-only suggestions, we just delete the range.
81
+ // The actual selection logic (onSelect) is handled in the render function's command prop.
82
+ editor.chain().focus().deleteRange(range).run();
83
+ },
84
+ render: () => {
85
+ let component;
86
+ let popup = null;
87
+ let container = null;
88
+ return {
89
+ onStart: (props) => {
90
+ component = new ReactRenderer(CommandList, {
91
+ props: {
92
+ ...props,
93
+ // Fix 1: Explicitly pass the command prop expected by CommandList
94
+ command: (item) => {
95
+ // If external callback provided, use it
96
+ if (onSelect) {
97
+ onSelect(item);
98
+ }
99
+ // CRITICAL: Call the original command to close the popup and delete the "/"
100
+ props.command(item);
101
+ },
102
+ items: props.items,
103
+ },
104
+ editor: props.editor,
105
+ });
106
+ // Create container for popup
107
+ container = document.createElement('div');
108
+ container.style.position = 'absolute';
109
+ container.style.zIndex = '9999';
110
+ container.style.display = 'none'; // Hidden until positioned
111
+ document.body.appendChild(container);
112
+ // Mount component
113
+ // @ts-ignore
114
+ container.appendChild(component.element);
115
+ // Position it
116
+ const coords = props.clientRect?.();
117
+ if (coords && container) {
118
+ container.style.display = 'block';
119
+ container.style.left = `${coords.left}px`;
120
+ container.style.top = `${coords.bottom + 10}px`;
121
+ }
122
+ },
123
+ onUpdate(props) {
124
+ component.updateProps({
125
+ ...props,
126
+ // Fix 2: Ensure command prop is preserved during updates
127
+ command: (item) => {
128
+ if (onSelect) {
129
+ onSelect(item);
130
+ }
131
+ props.command(item);
132
+ },
133
+ items: props.items,
134
+ });
135
+ const coords = props.clientRect?.();
136
+ if (coords && container) {
137
+ container.style.left = `${coords.left}px`;
138
+ container.style.top = `${coords.bottom + 10}px`;
139
+ }
140
+ },
141
+ onKeyDown(props) {
142
+ if (props.event.key === 'Escape') {
143
+ if (container) {
144
+ container.remove();
145
+ container = null;
146
+ }
147
+ return true;
148
+ }
149
+ // @ts-ignore
150
+ return component.ref?.onKeyDown(props);
151
+ },
152
+ onExit() {
153
+ if (container) {
154
+ container.remove();
155
+ container = null;
156
+ }
157
+ component.destroy();
158
+ },
159
+ };
160
+ },
161
+ });
@@ -0,0 +1 @@
1
+ export declare function uploadFile(file: File): Promise<string>;
@@ -0,0 +1,49 @@
1
+ // Utility to upload files reusing existing API logic
2
+ const DEFAULT_ENDPOINT = ((process.env.NEXT_PUBLIC_NEXTMIN_API_URL || '') + '/files').replace(/\/+$/, '') || '/files';
3
+ const defaultAuthHeaders = () => {
4
+ if (typeof window === 'undefined')
5
+ return {};
6
+ let token;
7
+ let apiKey;
8
+ try {
9
+ const raw = localStorage.getItem('nextmin.user');
10
+ if (raw) {
11
+ const u = JSON.parse(raw);
12
+ token = u?.token ?? u?.data?.token;
13
+ }
14
+ }
15
+ catch { }
16
+ token = token ?? localStorage.getItem('nextmin.token') ?? undefined;
17
+ apiKey =
18
+ localStorage.getItem('nextmin.apiKey') ??
19
+ process.env.NEXT_PUBLIC_NEXTMIN_API_KEY;
20
+ const h = {};
21
+ if (token)
22
+ h.Authorization = `Bearer ${token}`;
23
+ if (apiKey)
24
+ h['x-api-key'] = apiKey;
25
+ return h;
26
+ };
27
+ export async function uploadFile(file) {
28
+ const headers = defaultAuthHeaders();
29
+ const endpoint = DEFAULT_ENDPOINT;
30
+ // XHR for progress support if needed, but fetch is simpler for now
31
+ const formData = new FormData();
32
+ formData.append('file', file);
33
+ const response = await fetch(endpoint, {
34
+ method: 'POST',
35
+ headers: {
36
+ ...headers
37
+ // Content-Type is set automatically by fetch with FormData
38
+ },
39
+ body: formData
40
+ });
41
+ if (!response.ok) {
42
+ throw new Error('Upload failed');
43
+ }
44
+ const json = await response.json();
45
+ if (!json.success || !json.data || !json.data.length) {
46
+ throw new Error(json.message || 'Upload failed');
47
+ }
48
+ return json.data[0].url;
49
+ }