@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,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
+ }
@@ -0,0 +1,460 @@
1
+ .editor-input {
2
+ min-height: 150px;
3
+ resize: none;
4
+ font-size: 15px;
5
+ caret-color: rgb(5, 5, 5);
6
+ display: block;
7
+ position: relative;
8
+ tab-size: 1;
9
+ outline: 0;
10
+ padding: 10px;
11
+ caret-color: #444;
12
+ }
13
+
14
+ .editor-placeholder {
15
+ color: #999;
16
+ overflow: hidden;
17
+ position: absolute;
18
+ text-overflow: ellipsis;
19
+ top: 15px;
20
+ left: 10px;
21
+ font-size: 15px;
22
+ user-select: none;
23
+ display: inline-block;
24
+ pointer-events: none;
25
+ }
26
+
27
+ .editor-text-bold {
28
+ font-weight: bold;
29
+ }
30
+
31
+ .editor-text-italic {
32
+ font-style: italic;
33
+ }
34
+
35
+ .editor-text-underline {
36
+ text-decoration: underline !important;
37
+ }
38
+
39
+ .image-resizer {
40
+ display: block;
41
+ width: 12px;
42
+ height: 12px;
43
+ position: absolute;
44
+ background-color: rgb(60, 132, 244);
45
+ border: 1px solid #fff;
46
+ z-index: 100 !important;
47
+ box-shadow: 0 0 2px rgba(0, 0, 0, 0.5);
48
+ }
49
+
50
+ .editor-text-strikethrough {
51
+ text-decoration: line-through;
52
+ }
53
+
54
+ .editor-text-underlineStrikethrough {
55
+ text-decoration: underline line-through;
56
+ }
57
+
58
+ .editor-text-code {
59
+ background-color: rgb(240, 242, 245);
60
+ padding: 1px 0.25rem;
61
+ font-family: Menlo, Consolas, Monaco, monospace;
62
+ font-size: 94%;
63
+ }
64
+
65
+ .editor-link {
66
+ color: rgb(33, 111, 219);
67
+ text-decoration: none;
68
+ }
69
+
70
+ .editor-code {
71
+ background-color: rgb(240, 242, 245);
72
+ font-family: Menlo, Consolas, Monaco, monospace;
73
+ display: block;
74
+ padding: 8px 8px 8px 52px;
75
+ line-height: 1.53;
76
+ font-size: 13px;
77
+ margin: 0;
78
+ margin-top: 8px;
79
+ margin-bottom: 8px;
80
+ tab-size: 2;
81
+ overflow-x: auto;
82
+ position: relative;
83
+ }
84
+
85
+ .editor-code:before {
86
+ content: attr(data-gutter);
87
+ position: absolute;
88
+ background-color: #eee;
89
+ left: 0;
90
+ top: 0;
91
+ border-right: 1px solid #ccc;
92
+ padding: 8px;
93
+ color: #777;
94
+ white-space: pre-wrap;
95
+ text-align: right;
96
+ min-width: 25px;
97
+ }
98
+
99
+ .editor-paragraph {
100
+ margin: 0;
101
+ margin-bottom: 8px;
102
+ position: relative;
103
+ }
104
+
105
+ .editor-heading-h1 {
106
+ font-size: 24px;
107
+ color: rgb(5, 5, 5);
108
+ font-weight: bold;
109
+ margin: 0;
110
+ margin-bottom: 12px;
111
+ padding: 0;
112
+ }
113
+
114
+ .editor-heading-h2 {
115
+ font-size: 15px;
116
+ color: rgb(101, 103, 107);
117
+ font-weight: bold;
118
+ margin: 0;
119
+ margin-top: 10px;
120
+ padding: 0;
121
+ text-transform: uppercase;
122
+ }
123
+
124
+ .editor-quote {
125
+ margin: 0;
126
+ margin-left: 20px;
127
+ font-size: 15px;
128
+ color: rgb(101, 103, 107);
129
+ border-left-color: rgb(206, 208, 212);
130
+ border-left-width: 4px;
131
+ border-left-style: solid;
132
+ padding-left: 16px;
133
+ }
134
+
135
+ .editor-list-ol {
136
+ padding: 0;
137
+ margin: 0;
138
+ margin-left: 20px;
139
+ list-style-type: decimal;
140
+ }
141
+
142
+ .editor-list-ul {
143
+ padding: 0;
144
+ margin: 0;
145
+ margin-left: 20px;
146
+ list-style-type: disc;
147
+ }
148
+
149
+ .editor-listitem {
150
+ margin: 8px 32px 8px 32px;
151
+ }
152
+
153
+ .editor-nested-listitem {
154
+ list-style-type: none;
155
+ }
156
+
157
+ /* Image Resizer Styles */
158
+ .editor-image {
159
+ cursor: default;
160
+ display: inline-block;
161
+ position: relative;
162
+ user-select: none;
163
+ }
164
+
165
+ .editor-image img {
166
+ max-width: 100%;
167
+ cursor: default;
168
+ }
169
+
170
+ .editor-image.focused {
171
+ outline: 2px solid rgb(60, 132, 244);
172
+ user-select: none;
173
+ }
174
+
175
+
176
+
177
+ .image-resizer-n {
178
+ top: -6px;
179
+ left: 48%;
180
+ cursor: n-resize;
181
+ }
182
+
183
+ .image-resizer-ne {
184
+ top: -6px;
185
+ right: -6px;
186
+ cursor: ne-resize;
187
+ }
188
+
189
+ .image-resizer-e {
190
+ bottom: 48%;
191
+ right: -6px;
192
+ cursor: e-resize;
193
+ }
194
+
195
+ .image-resizer-se {
196
+ bottom: -6px;
197
+ right: -6px;
198
+ cursor: se-resize;
199
+ }
200
+
201
+ .image-resizer-s {
202
+ bottom: -6px;
203
+ left: 48%;
204
+ cursor: s-resize;
205
+ }
206
+
207
+ .image-resizer-sw {
208
+ bottom: -6px;
209
+ left: -6px;
210
+ cursor: sw-resize;
211
+ }
212
+
213
+ .image-resizer-w {
214
+ bottom: 48%;
215
+ left: -6px;
216
+ cursor: w-resize;
217
+ }
218
+
219
+ .image-resizer-nw {
220
+ top: -6px;
221
+ left: -6px;
222
+ cursor: nw-resize;
223
+ }
224
+
225
+ .editor-table {
226
+ border-collapse: collapse;
227
+ border-spacing: 0;
228
+ max-width: 100%;
229
+ overflow-y: scroll;
230
+ table-layout: fixed;
231
+ width: 100%;
232
+ }
233
+
234
+ .editor-tableRow {}
235
+
236
+ .editor-tableCell {
237
+ border: 1px solid #bbb;
238
+ min-width: 75px;
239
+ padding: 8px;
240
+ position: relative;
241
+ vertical-align: top;
242
+ z-index: 0;
243
+ }
244
+
245
+ .editor-tableCellHeader {
246
+ background-color: #f2f3f5;
247
+ text-align: start;
248
+ }
249
+
250
+ .editor-layout-container {
251
+ display: grid;
252
+ gap: 10px;
253
+ margin: 10px 0;
254
+ }
255
+
256
+ .editor-layout-item {
257
+ border: 1px dashed #ddd;
258
+ padding: 8px;
259
+ min-height: 50px;
260
+ }
261
+
262
+ /* ========================================================================= */
263
+ /* MERGED: NextMin Custom Editor Components (Grid, Layout, Image) */
264
+ /* ========================================================================= */
265
+
266
+ /* Editor Grid System (replacing Tailwind) */
267
+
268
+ /* Grid Container */
269
+ .nm-editor-grid {
270
+ display: grid;
271
+ grid-template-columns: repeat(1, minmax(0, 1fr));
272
+ gap: 1.5rem;
273
+ /* gap-6 equivalent */
274
+ margin-top: 1rem;
275
+ margin-bottom: 1rem;
276
+ }
277
+
278
+ @media (min-width: 640px) {
279
+ .nm-editor-grid {
280
+ grid-template-columns: repeat(2, minmax(0, 1fr));
281
+ }
282
+ }
283
+
284
+ @media (min-width: 768px) {
285
+ .nm-editor-grid {
286
+ grid-template-columns: repeat(3, minmax(0, 1fr));
287
+ }
288
+ }
289
+
290
+ /* Layout Rows */
291
+ .nm-editor-row-1 {
292
+ display: grid;
293
+ grid-template-columns: repeat(1, minmax(0, 1fr));
294
+ gap: 1rem;
295
+ margin-top: 1.5rem;
296
+ margin-bottom: 1.5rem;
297
+ }
298
+
299
+ .nm-editor-row-2 {
300
+ display: grid;
301
+ grid-template-columns: repeat(1, minmax(0, 1fr));
302
+ gap: 1rem;
303
+ margin-top: 1rem;
304
+ margin-bottom: 1rem;
305
+ }
306
+
307
+ @media (min-width: 768px) {
308
+ .nm-editor-row-2 {
309
+ grid-template-columns: repeat(2, minmax(0, 1fr));
310
+ }
311
+ }
312
+
313
+ .nm-editor-row-3 {
314
+ display: grid;
315
+ grid-template-columns: repeat(1, minmax(0, 1fr));
316
+ gap: 1rem;
317
+ margin-top: 1rem;
318
+ margin-bottom: 1rem;
319
+ }
320
+
321
+ @media (min-width: 768px) {
322
+ .nm-editor-row-3 {
323
+ grid-template-columns: repeat(3, minmax(0, 1fr));
324
+ }
325
+ }
326
+
327
+
328
+ /* Grid Item Wrapper (div) */
329
+ /* Grid Item (Card) */
330
+ .nm-editor-grid-card {
331
+ /* Container Styles */
332
+ border-width: 1px;
333
+ border-style: solid;
334
+ border-color: #e5e7eb;
335
+ /* gray-200 */
336
+ border-radius: var(--nm-grid-radius, 0.5rem);
337
+ /* rounded-lg with override */
338
+ box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
339
+ /* shadow-sm */
340
+ background-color: var(--nm-grid-card-bg, #ffffff);
341
+ overflow: hidden;
342
+
343
+ /* Transition */
344
+ transition-property: color, background-color, border-color, text-decoration-color, fill, stroke;
345
+ transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
346
+ transition-duration: 150ms;
347
+
348
+ /* Content/Layout Styles */
349
+ display: flex;
350
+ flex-direction: column;
351
+ align-items: center;
352
+ justify-content: center;
353
+ text-align: center;
354
+ width: 100%;
355
+ height: 100%;
356
+ padding: 10px;
357
+ /* p-5 */
358
+
359
+ /* Text Styles */
360
+ font-weight: 500;
361
+ font-size: 1rem;
362
+ color: #374151;
363
+ /* gray-700 */
364
+ text-decoration: none !important;
365
+ cursor: pointer;
366
+ }
367
+
368
+ .nm-editor-grid-card:hover {
369
+ background-color: #f9fafb;
370
+ /* gray-50 */
371
+ }
372
+
373
+ /* Ensure content inside grid cards is compact */
374
+ .nm-editor-grid-card p {
375
+ margin: 0 !important;
376
+ }
377
+
378
+ /* Dark Mode support */
379
+ .dark .nm-editor-grid-card {
380
+ border-color: #1f2937;
381
+ /* gray-800 */
382
+ background-color: #18181b;
383
+ /* zinc-900 */
384
+ color: #e5e7eb;
385
+ /* gray-200 */
386
+ }
387
+
388
+ .dark .nm-editor-grid-card:hover {
389
+ background-color: #27272a;
390
+ /* zinc-800 */
391
+ }
392
+
393
+
394
+ /* Layout Column */
395
+ .nm-editor-col {
396
+ width: 100%;
397
+ height: 100%;
398
+ }
399
+
400
+ /* Image Component Styles */
401
+ .nm-editor-w-full {
402
+ width: 100%;
403
+ }
404
+
405
+ .nm-editor-img {
406
+ border-radius: 0.125rem;
407
+ /* rounded-sm */
408
+ max-width: 100%;
409
+ }
410
+
411
+ .nm-editor-img-wrapper {
412
+ position: relative;
413
+ line-height: 1;
414
+ }
415
+
416
+ .nm-editor-img-container {
417
+ position: relative;
418
+ display: inline-flex;
419
+ }
420
+
421
+ .nm-editor-ring-selected {
422
+ border-radius: 0.125rem;
423
+ box-shadow: 0 0 0 2px var(--nm-primary, #3b82f6);
424
+ /* ring-2 ring-primary */
425
+ }
426
+
427
+ .nm-editor-group {
428
+ /* placeholder for group if needed */
429
+ }
430
+
431
+ /* Table Resizing */
432
+ .resize-cursor {
433
+ cursor: col-resize;
434
+ }
435
+
436
+ .column-resize-handle {
437
+ background-color: #3b82f6;
438
+ /* blue-500 */
439
+ bottom: -2px;
440
+ pointer-events: none;
441
+ position: absolute;
442
+ right: -2px;
443
+ top: 0;
444
+ width: 4px;
445
+ z-index: 10;
446
+ }
447
+
448
+
449
+ /* Standalone Container */
450
+ .nm-editor-container {
451
+ display: block;
452
+ width: 100%;
453
+ margin-top: 1.5rem;
454
+ margin-bottom: 1.5rem;
455
+ overflow: hidden;
456
+ /* Default padding if user puts text directly */
457
+ padding: 1rem;
458
+ border: 1px dashed transparent;
459
+ /* Helps selection visualization if needed */
460
+ }
@@ -0,0 +1 @@
1
+ export declare function uploadFile(file: File): Promise<string | null>;