@airoom/nextmin-react 1.4.0 → 1.4.2

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 (42) hide show
  1. package/dist/components/SchemaForm.js +17 -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 +11 -0
  5. package/dist/components/editor/TiptapEditor.js +330 -0
  6. package/dist/components/editor/Toolbar.d.ts +7 -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/DistrictGridModal.d.ts +12 -0
  11. package/dist/components/editor/components/DistrictGridModal.js +67 -0
  12. package/dist/components/editor/components/ImageBubbleMenu.d.ts +6 -0
  13. package/dist/components/editor/components/ImageBubbleMenu.js +15 -0
  14. package/dist/components/editor/components/ImageComponent.d.ts +3 -0
  15. package/dist/components/editor/components/ImageComponent.js +45 -0
  16. package/dist/components/editor/components/SchemaInsertionModal.d.ts +15 -0
  17. package/dist/components/editor/components/SchemaInsertionModal.js +91 -0
  18. package/dist/components/editor/components/TableBubbleMenu.d.ts +6 -0
  19. package/dist/components/editor/components/TableBubbleMenu.js +8 -0
  20. package/dist/components/editor/extensions/Container.d.ts +2 -0
  21. package/dist/components/editor/extensions/Container.js +51 -0
  22. package/dist/components/editor/extensions/Grid.d.ts +3 -0
  23. package/dist/components/editor/extensions/Grid.js +89 -0
  24. package/dist/components/editor/extensions/Layout.d.ts +3 -0
  25. package/dist/components/editor/extensions/Layout.js +116 -0
  26. package/dist/components/editor/extensions/ResizableImage.d.ts +1 -0
  27. package/dist/components/editor/extensions/ResizableImage.js +52 -0
  28. package/dist/components/editor/extensions/SlashCommand.d.ts +15 -0
  29. package/dist/components/editor/extensions/SlashCommand.js +161 -0
  30. package/dist/components/editor/utils/upload.d.ts +1 -0
  31. package/dist/components/editor/utils/upload.js +49 -0
  32. package/dist/editor.css +460 -0
  33. package/dist/lib/upload.d.ts +1 -0
  34. package/dist/lib/upload.js +53 -0
  35. package/dist/lib/utils.d.ts +2 -0
  36. package/dist/lib/utils.js +5 -0
  37. package/dist/nextmin.css +1 -1
  38. package/dist/router/NextMinRouter.d.ts +1 -0
  39. package/dist/router/NextMinRouter.js +1 -0
  40. package/dist/views/list/useListData.js +4 -0
  41. package/package.json +34 -8
  42. package/tsconfig.json +8 -3
@@ -0,0 +1,67 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { useState, useEffect } from 'react';
4
+ import { Modal, ModalContent, ModalHeader, ModalBody, ModalFooter, Button, } from '@heroui/react';
5
+ import { RefMultiSelect } from '../../RefMultiSelect';
6
+ import { RefSingleSelect } from '../../RefSingleSelect';
7
+ import { cn } from '../../../lib/utils';
8
+ import { api } from '../../../lib/api';
9
+ export const DistrictGridModal = ({ isOpen, onClose, onInsert, currentSpeciality, }) => {
10
+ const [baseType, setBaseType] = useState('doctors');
11
+ const [selectedDistricts, setSelectedDistricts] = useState([]);
12
+ const [selectedSpecialityId, setSelectedSpecialityId] = useState('');
13
+ const [specialitySlug, setSpecialitySlug] = useState('');
14
+ // Fetch speciality data when ID changes
15
+ useEffect(() => {
16
+ if (!selectedSpecialityId) {
17
+ setSpecialitySlug('');
18
+ return;
19
+ }
20
+ const fetchSpeciality = async () => {
21
+ try {
22
+ const res = await api.get('Specialities', selectedSpecialityId);
23
+ const speciality = res.data || res;
24
+ setSpecialitySlug(speciality?.slug || '');
25
+ }
26
+ catch (err) {
27
+ console.error('Failed to fetch speciality:', err);
28
+ setSpecialitySlug('');
29
+ }
30
+ };
31
+ fetchSpeciality();
32
+ }, [selectedSpecialityId]);
33
+ const handleInsert = () => {
34
+ onInsert({
35
+ districts: selectedDistricts,
36
+ baseType,
37
+ specialitySlug: specialitySlug
38
+ });
39
+ onClose();
40
+ // Reset state after a short delay
41
+ setTimeout(() => {
42
+ setBaseType('doctors');
43
+ setSelectedDistricts([]);
44
+ setSelectedSpecialityId('');
45
+ setSpecialitySlug('');
46
+ }, 200);
47
+ };
48
+ const baseFrontendUrl = process.env.NEXT_PUBLIC_FRONTEND_URL || '';
49
+ const cleanBase = baseFrontendUrl.replace(/\/$/, '');
50
+ // Generate preview URL
51
+ const getPreviewUrl = (districtSlug) => {
52
+ const parts = [cleanBase, baseType, districtSlug];
53
+ if (specialitySlug) {
54
+ parts.push(specialitySlug);
55
+ }
56
+ return parts.join('/');
57
+ };
58
+ return (_jsx(Modal, { isOpen: isOpen, onClose: onClose, size: "2xl", classNames: {
59
+ base: "bg-white dark:bg-zinc-950 border border-gray-200 dark:border-gray-800",
60
+ header: "border-b border-gray-200 dark:border-gray-800",
61
+ footer: "border-t border-gray-200 dark:border-gray-800",
62
+ }, children: _jsx(ModalContent, { children: _jsxs(_Fragment, { children: [_jsx(ModalHeader, { children: "Insert District Grid" }), _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: "Base Type" }), _jsxs("div", { className: "flex gap-4", children: [_jsx("button", { onClick: () => setBaseType('doctors'), className: cn("px-4 py-2 rounded-lg border text-sm font-medium transition-colors", baseType === 'doctors'
63
+ ? "bg-primary-50 border-primary-500 text-primary-700 dark:bg-primary-900/20 dark:text-primary-400"
64
+ : "bg-transparent border-gray-200 dark:border-gray-800 hover:bg-gray-50 dark:hover:bg-gray-900"), children: "Doctors" }), _jsx("button", { onClick: () => setBaseType('hospitals'), className: cn("px-4 py-2 rounded-lg border text-sm font-medium transition-colors", baseType === 'hospitals'
65
+ ? "bg-primary-50 border-primary-500 text-primary-700 dark:bg-primary-900/20 dark:text-primary-400"
66
+ : "bg-transparent border-gray-200 dark:border-gray-800 hover:bg-gray-50 dark:hover:bg-gray-900"), children: "Hospitals" })] })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx("label", { className: "text-sm font-medium", children: "Select Districts" }), _jsx(RefMultiSelect, { name: "districts", label: "Search & Select Districts", refModel: "Districts", value: selectedDistricts, onChange: (ids) => setSelectedDistricts(ids), pageSize: 64 }), _jsx("p", { className: "text-xs text-gray-400", children: "Select one or more districts to create grid items." })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx("label", { className: "text-sm font-medium", children: "Select Speciality (Optional)" }), _jsx(RefSingleSelect, { name: "speciality", label: "Search & Select Speciality", refModel: "Specialities", showKey: "name", value: selectedSpecialityId, onChange: (id) => setSelectedSpecialityId(id || ''), pageSize: 50 }), _jsx("p", { className: "text-xs text-gray-400", children: "Leave empty to create URLs without speciality." }), specialitySlug && (_jsx("div", { className: "mt-1 px-2 py-1 bg-primary-50 dark:bg-primary-900/20 rounded border border-primary-200 dark:border-primary-800", children: _jsxs("p", { className: "text-xs text-primary-700 dark:text-primary-400", children: ["Slug: ", _jsx("code", { className: "font-mono", children: specialitySlug })] }) }))] }), selectedDistricts.length > 0 && (_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx("label", { className: "text-sm font-medium", children: "URL Preview" }), _jsxs("div", { className: "px-3 py-2 bg-gray-50 dark:bg-gray-900 rounded-lg border border-gray-200 dark:border-gray-800 max-h-32 overflow-y-auto", children: [_jsx("p", { className: "text-xs text-gray-500 mb-2", children: "Example URL pattern (actual district slugs will be used):" }), _jsx("code", { className: "text-xs text-gray-700 dark:text-gray-300", children: getPreviewUrl('[district-slug]') })] })] }))] }), _jsxs(ModalFooter, { children: [_jsx(Button, { variant: "flat", onPress: onClose, children: "Cancel" }), _jsx(Button, { color: "primary", onPress: handleInsert, isDisabled: selectedDistricts.length === 0, children: "Insert Grid" })] })] }) }) }));
67
+ };
@@ -0,0 +1,6 @@
1
+ import { Editor } from '@tiptap/react';
2
+ interface ImageBubbleMenuProps {
3
+ editor: Editor | null;
4
+ }
5
+ export declare const ImageBubbleMenu: ({ editor }: ImageBubbleMenuProps) => import("react/jsx-runtime").JSX.Element | null;
6
+ export {};
@@ -0,0 +1,15 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { BubbleMenu } from '@tiptap/react/menus';
3
+ import { Minimize, AlignLeft, AlignCenter, AlignRight, StretchHorizontal } from 'lucide-react';
4
+ import { cn } from '../../../lib/utils';
5
+ export const ImageBubbleMenu = ({ editor }) => {
6
+ if (!editor)
7
+ return null;
8
+ return (_jsxs(BubbleMenu, { editor: editor, pluginKey: "imageBubbleMenu", shouldShow: ({ editor }) => editor.isActive('image'),
9
+ // @ts-ignore
10
+ tippyOptions: {
11
+ duration: 100,
12
+ zIndex: 99, // Ensure it sits above other elements
13
+ maxWidth: 'none',
14
+ }, 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 z-50", children: [_jsx("button", { type: "button", onClick: () => editor.chain().focus().updateAttributes('image', { width: '100%', height: null }).run(), className: cn("p-1.5 rounded hover:bg-gray-100 dark:hover:bg-gray-700 text-gray-700 dark:text-gray-200", editor.getAttributes('image').width === '100%' && "bg-gray-200 dark:bg-gray-600"), title: "Full Width", children: _jsx(StretchHorizontal, { size: 16 }) }), _jsx("button", { type: "button", onClick: () => editor.chain().focus().updateAttributes('image', { width: null, height: null }).run(), className: "p-1.5 rounded hover:bg-gray-100 dark:hover:bg-gray-700 text-gray-700 dark:text-gray-200", title: "Original Size", children: _jsx(Minimize, { size: 16 }) }), _jsx("div", { className: "w-[1px] h-4 bg-gray-300 dark:bg-gray-600 mx-1" }), _jsx("button", { type: "button", onClick: () => editor.chain().focus().setTextAlign('left').run(), className: cn("p-1.5 rounded hover:bg-gray-100 dark:hover:bg-gray-700 text-gray-700 dark:text-gray-200", editor.isActive({ textAlign: 'left' }) && "bg-gray-200 dark:bg-gray-600"), title: "Align Left", children: _jsx(AlignLeft, { size: 16 }) }), _jsx("button", { type: "button", onClick: () => editor.chain().focus().setTextAlign('center').run(), className: cn("p-1.5 rounded hover:bg-gray-100 dark:hover:bg-gray-700 text-gray-700 dark:text-gray-200", editor.isActive({ textAlign: 'center' }) && "bg-gray-200 dark:bg-gray-600"), title: "Align Center", children: _jsx(AlignCenter, { size: 16 }) }), _jsx("button", { type: "button", onClick: () => editor.chain().focus().setTextAlign('right').run(), className: cn("p-1.5 rounded hover:bg-gray-100 dark:hover:bg-gray-700 text-gray-700 dark:text-gray-200", editor.isActive({ textAlign: 'right' }) && "bg-gray-200 dark:bg-gray-600"), title: "Align Right", children: _jsx(AlignRight, { size: 16 }) }), _jsx("div", { className: "w-[1px] h-4 bg-gray-300 dark:bg-gray-600 mx-1" }), _jsx("button", { type: "button", onClick: () => editor.chain().focus().updateAttributes('image', { objectFit: 'contain' }).run(), className: cn("p-1.5 rounded hover:bg-gray-100 dark:hover:bg-gray-700 text-gray-700 dark:text-gray-200 text-xs font-medium", editor.getAttributes('image').objectFit === 'contain' && "bg-gray-200 dark:bg-gray-600"), title: "Contain", children: "Fit" }), _jsx("button", { type: "button", onClick: () => editor.chain().focus().updateAttributes('image', { objectFit: 'cover' }).run(), className: cn("p-1.5 rounded hover:bg-gray-100 dark:hover:bg-gray-700 text-gray-700 dark:text-gray-200 text-xs font-medium", editor.getAttributes('image').objectFit === 'cover' && "bg-gray-200 dark:bg-gray-600"), title: "Cover", children: "Cover" }), _jsx("button", { type: "button", onClick: () => editor.chain().focus().updateAttributes('image', { objectFit: 'fill' }).run(), className: cn("p-1.5 rounded hover:bg-gray-100 dark:hover:bg-gray-700 text-gray-700 dark:text-gray-200 text-xs font-medium", editor.getAttributes('image').objectFit === 'fill' && "bg-gray-200 dark:bg-gray-600"), title: "Fill", children: "Fill" })] }));
15
+ };
@@ -0,0 +1,3 @@
1
+ import { NodeViewProps } from '@tiptap/react';
2
+ import React from 'react';
3
+ export declare const ImageComponent: React.FC<NodeViewProps>;
@@ -0,0 +1,45 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { NodeViewWrapper } from '@tiptap/react';
3
+ import { useState, useEffect } from 'react';
4
+ import { ResizableBox } from 'react-resizable';
5
+ import clsx from 'clsx';
6
+ export const ImageComponent = (props) => {
7
+ const { node, updateAttributes, selected } = props;
8
+ const { src, alt, width, height, textAlign, objectFit } = node.attrs;
9
+ // Determine if we are in "Full Width" mode
10
+ const isFullWidth = width === '100%';
11
+ const [w, setW] = useState(width || 'auto');
12
+ const [h, setH] = useState(height || 'auto');
13
+ useEffect(() => {
14
+ setW(width || 'auto');
15
+ setH(height || 'auto');
16
+ }, [width, height]);
17
+ const onResize = (e, { size }) => {
18
+ setW(size.width);
19
+ setH(size.height);
20
+ };
21
+ const onResizeStop = (e, { size }) => {
22
+ updateAttributes({
23
+ width: size.width,
24
+ height: size.height,
25
+ });
26
+ };
27
+ const getSimulatedWidth = () => (typeof w === 'number' ? w : 300);
28
+ const getSimulatedHeight = () => (typeof h === 'number' ? h : 300);
29
+ const alignmentClass = textAlign === 'center'
30
+ ? 'image-align-center'
31
+ : textAlign === 'right'
32
+ ? 'image-align-right'
33
+ : textAlign === 'left'
34
+ ? 'image-align-left'
35
+ : 'image-align-default';
36
+ const renderImage = () => (_jsx("img", { src: src, alt: alt, style: {
37
+ width: isFullWidth ? '100%' : '100%',
38
+ height: isFullWidth ? 'auto' : '100%',
39
+ display: 'block',
40
+ objectFit: objectFit || 'contain',
41
+ }, className: clsx("nm-editor-img", isFullWidth && "nm-editor-w-full"), draggable: false }));
42
+ return (_jsx(NodeViewWrapper, { className: clsx('image-component-wrapper nm-editor-img-wrapper', alignmentClass, isFullWidth && 'nm-editor-w-full'), children: _jsx("div", { className: clsx('nm-editor-img-container group', selected && !isFullWidth && 'nm-editor-ring-selected', isFullWidth && 'nm-editor-w-full'), children: isFullWidth ? (_jsx("div", { className: clsx("nm-editor-w-full", selected && "nm-editor-ring-selected"), children: renderImage() })) : (_jsx(ResizableBox, { width: getSimulatedWidth(), height: getSimulatedHeight(), onResize: onResize, onResizeStop: onResizeStop, lockAspectRatio: true, draggableOpts: { enableUserSelectHack: false }, resizeHandles: selected ? ['sw', 'se', 'nw', 'ne'] : [], handle: (h, ref) => (_jsx("div", { className: `image-resizer image-resizer-${h}`,
43
+ // @ts-ignore
44
+ ref: ref })), children: renderImage() })) }) }));
45
+ };
@@ -0,0 +1,15 @@
1
+ import { SchemaDef } from '../../../lib/types';
2
+ interface SchemaInsertionModalProps {
3
+ isOpen: boolean;
4
+ onClose: () => void;
5
+ onInsert: (config: {
6
+ viewType: 'grid' | 'list';
7
+ template: string;
8
+ ids: string[];
9
+ urlPattern: string;
10
+ }) => void;
11
+ schema: SchemaDef | null;
12
+ availableSchemas: SchemaDef[];
13
+ }
14
+ export declare const SchemaInsertionModal: ({ isOpen, onClose, onInsert, schema, availableSchemas, }: SchemaInsertionModalProps) => import("react/jsx-runtime").JSX.Element | null;
15
+ export {};
@@ -0,0 +1,91 @@
1
+ 'use client';
2
+ import { jsxs as _jsxs, jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import React, { 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
+ // Helper to get nested keys with recursion limit
11
+ const getAllKeys = (attributes, allSchemas, prefix = '', depth = 0) => {
12
+ if (depth > 2)
13
+ return []; // Prevent infinite recursion
14
+ let keys = [];
15
+ Object.keys(attributes).forEach(key => {
16
+ const attr = attributes[key];
17
+ const currentKey = prefix ? `${prefix}.${key}` : key;
18
+ keys.push(currentKey);
19
+ // Check for reference
20
+ let refModelName;
21
+ if (attr.ref) {
22
+ refModelName = attr.ref;
23
+ }
24
+ else if (Array.isArray(attr) && attr[0]?.ref) {
25
+ refModelName = attr[0].ref;
26
+ }
27
+ if (refModelName) {
28
+ const refSchema = allSchemas.find(s => s.modelName === refModelName);
29
+ if (refSchema) {
30
+ const subKeys = getAllKeys(refSchema.attributes, allSchemas, currentKey, depth + 1);
31
+ keys = [...keys, ...subKeys];
32
+ }
33
+ }
34
+ else if (typeof attr === 'object' && !attr.type && !Array.isArray(attr)) {
35
+ // Plain nested object (not an attribute definition itself, but a grouping)
36
+ // Check if it has type property? SchemaDef attributes are Record<string, Attribute>.
37
+ // Attribute can be AttributeBase or AttributeBase[].
38
+ // If it doesn't have 'type', it might be a nested object structure if supported.
39
+ // But based on types.ts, attributes is Record<string, Attribute>.
40
+ // So we strictly look for ref in AttributeBase.
41
+ // If keys are just grouping (e.g. meta: { title: ... }), then we recurse.
42
+ // But Mongoose schema defs usually have type.
43
+ // Let's assume structure is flat or standard Mongoose.
44
+ }
45
+ });
46
+ // Add common virtuals at appropriate levels if needed, or just top level
47
+ if (prefix === '') {
48
+ keys.push('id');
49
+ keys.push('createdAt');
50
+ keys.push('updatedAt');
51
+ }
52
+ return keys;
53
+ };
54
+ export const SchemaInsertionModal = ({ isOpen, onClose, onInsert, schema, availableSchemas, }) => {
55
+ const [viewType, setViewType] = useState('grid');
56
+ const [template, setTemplate] = useState('');
57
+ const [urlPattern, setUrlPattern] = useState('{slug}_{id}');
58
+ const [selectedIds, setSelectedIds] = useState([]);
59
+ const handleInsert = () => {
60
+ onInsert({ viewType, template, ids: selectedIds, urlPattern });
61
+ onClose();
62
+ setTimeout(() => {
63
+ setViewType('grid');
64
+ setTemplate('');
65
+ setUrlPattern('{slug}_{id}');
66
+ setSelectedIds([]);
67
+ }, 200);
68
+ };
69
+ const availableKeys = React.useMemo(() => {
70
+ if (!schema)
71
+ return [];
72
+ return getAllKeys(schema.attributes, availableSchemas);
73
+ }, [schema, availableSchemas]);
74
+ const addToTemplate = (key) => {
75
+ setTemplate(prev => `${prev} {${key}}`);
76
+ };
77
+ const addToUrlPattern = (key) => {
78
+ setUrlPattern(prev => `${prev}{${key}}`);
79
+ };
80
+ if (!schema)
81
+ return null;
82
+ return (_jsx(Modal, { isOpen: isOpen, onClose: onClose, size: "2xl", classNames: {
83
+ base: "bg-white dark:bg-zinc-950 border border-gray-200 dark:border-gray-800",
84
+ header: "border-b border-gray-200 dark:border-gray-800",
85
+ footer: "border-t border-gray-200 dark:border-gray-800",
86
+ }, 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'
87
+ ? "bg-primary-50 border-primary-500 text-primary-700 dark:bg-primary-900/20 dark:text-primary-400"
88
+ : "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'
89
+ ? "bg-primary-50 border-primary-500 text-primary-700 dark:bg-primary-900/20 dark:text-primary-400"
90
+ : "bg-transparent border-gray-200 dark:border-gray-800 hover:bg-gray-50"), children: "List (Rows)" })] })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_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("div", { className: "flex flex-wrap gap-2 mt-1", children: availableKeys.map(key => (_jsx("button", { onClick: () => addToTemplate(key), className: "px-2 py-1 text-xs bg-gray-100 dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-md hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors", children: key }, key))) })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_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" }), _jsx("div", { className: "flex flex-wrap gap-2 mt-1", children: availableKeys.map(key => (_jsx("button", { onClick: () => addToUrlPattern(key), className: "px-2 py-1 text-xs bg-gray-100 dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-md hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors", children: key }, key))) })] })] }), _jsxs(ModalFooter, { children: [_jsx(Button, { variant: "flat", onPress: onClose, children: "Cancel" }), _jsx(Button, { color: "primary", onPress: handleInsert, children: "Insert" })] })] }) }) }));
91
+ };
@@ -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
+ };