@airoom/nextmin-react 2.0.2 → 2.0.3
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 +57 -0
- package/dist/components/AddressAutocomplete.d.ts +4 -1
- package/dist/components/AddressAutocomplete.js +2 -2
- package/dist/components/AdminApp.js +86 -1
- package/dist/components/FileUploader.d.ts +3 -0
- package/dist/components/FileUploader.js +84 -28
- package/dist/components/PasswordInput.d.ts +2 -0
- package/dist/components/PasswordInput.js +2 -2
- package/dist/components/PhoneInput.d.ts +2 -0
- package/dist/components/PhoneInput.js +2 -2
- package/dist/components/QRCodeModal.d.ts +10 -0
- package/dist/components/QRCodeModal.js +153 -0
- package/dist/components/RefSelect.d.ts +4 -1
- package/dist/components/RefSelect.js +163 -65
- package/dist/components/SchemaForm.d.ts +2 -1
- package/dist/components/SchemaForm.js +132 -55
- package/dist/components/Sidebar.js +96 -7
- package/dist/components/TableFilters.js +1 -1
- package/dist/components/editor/TiptapEditor.js +43 -12
- package/dist/components/editor/Toolbar.js +195 -70
- package/dist/components/editor/components/CustomFontSizeModal.d.ts +13 -0
- package/dist/components/editor/components/CustomFontSizeModal.js +81 -0
- package/dist/components/editor/components/DistrictGridModal.js +1 -1
- package/dist/components/editor/components/SchemaInsertionModal.js +1 -1
- package/dist/components/editor/editor.css +2 -0
- package/dist/components/editor/extensions/FontSize.d.ts +19 -0
- package/dist/components/editor/extensions/FontSize.js +41 -0
- package/dist/components/viewer/DynamicViewer.js +50 -15
- package/dist/components/viewer/useViewDrawer.js +42 -2
- package/dist/hooks/useAppNavigation.d.ts +3 -0
- package/dist/hooks/useAppNavigation.js +37 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/lib/QueryBuilder.d.ts +3 -1
- package/dist/lib/QueryBuilder.js +10 -0
- package/dist/lib/api.d.ts +6 -0
- package/dist/lib/api.js +45 -24
- package/dist/lib/schemaUtils.js +1 -1
- package/dist/lib/types.d.ts +6 -1
- package/dist/nextmin.css +1 -1
- package/dist/providers/NextMinProvider.d.ts +5 -1
- package/dist/providers/NextMinProvider.js +12 -2
- package/dist/providers/themeResolver.d.ts +25 -0
- package/dist/providers/themeResolver.js +174 -0
- package/dist/router/NextMinRouter.d.ts +0 -1
- package/dist/router/NextMinRouter.js +0 -1
- package/dist/views/CreateEditPage.js +20 -11
- package/dist/views/DashboardPage.js +1 -1
- package/dist/views/ListPage.js +37 -6
- package/dist/views/SettingsEdit.js +1 -1
- package/dist/views/list/DataTableHero.d.ts +5 -5
- package/dist/views/list/DataTableHero.js +154 -41
- package/dist/views/list/ListHeader.js +9 -1
- package/package.json +4 -2
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { Extension } from '@tiptap/core';
|
|
2
|
+
export const FontSize = Extension.create({
|
|
3
|
+
name: 'fontSize',
|
|
4
|
+
addOptions() {
|
|
5
|
+
return {
|
|
6
|
+
types: ['textStyle'],
|
|
7
|
+
};
|
|
8
|
+
},
|
|
9
|
+
addGlobalAttributes() {
|
|
10
|
+
return [
|
|
11
|
+
{
|
|
12
|
+
types: this.options.types,
|
|
13
|
+
attributes: {
|
|
14
|
+
fontSize: {
|
|
15
|
+
default: null,
|
|
16
|
+
parseHTML: element => element.style.fontSize?.replace(/['"]/g, '') || null,
|
|
17
|
+
renderHTML: attributes => {
|
|
18
|
+
if (!attributes.fontSize) {
|
|
19
|
+
return {};
|
|
20
|
+
}
|
|
21
|
+
return {
|
|
22
|
+
style: `font-size: ${attributes.fontSize}`,
|
|
23
|
+
};
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
];
|
|
29
|
+
},
|
|
30
|
+
addCommands() {
|
|
31
|
+
return {
|
|
32
|
+
setFontSize: (fontSize) => ({ chain }) => {
|
|
33
|
+
const formatted = typeof fontSize === 'number' || /^\d+$/.test(fontSize) ? `${fontSize}px` : fontSize;
|
|
34
|
+
return chain().setMark('textStyle', { fontSize: formatted }).run();
|
|
35
|
+
},
|
|
36
|
+
unsetFontSize: () => ({ chain }) => {
|
|
37
|
+
return chain().setMark('textStyle', { fontSize: null }).removeEmptyTextStyle().run();
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
},
|
|
41
|
+
});
|
|
@@ -52,25 +52,46 @@ function isLikelyImageUrl(s) {
|
|
|
52
52
|
return IMG_EXT_RE.test(path.split('.').pop() || '');
|
|
53
53
|
}
|
|
54
54
|
}
|
|
55
|
-
function
|
|
55
|
+
function isImageKeyOrAttr(key, attr) {
|
|
56
|
+
if (key) {
|
|
57
|
+
const k = key.toLowerCase();
|
|
58
|
+
if (['image', 'img', 'avatar', 'photo', 'thumbnail', 'logo', 'cover', 'banner', 'pic', 'picture', 'icon'].some((term) => k.includes(term))) {
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
const a = Array.isArray(attr) ? attr[0] : attr;
|
|
63
|
+
const t = String(a?.type ?? '').toLowerCase();
|
|
64
|
+
const f = String(a?.format ?? '').toLowerCase();
|
|
65
|
+
return t === 'image' || f === 'image' || t === 'media' || f === 'media';
|
|
66
|
+
}
|
|
67
|
+
function extractUrl(v, key, attr) {
|
|
56
68
|
if (!v)
|
|
57
69
|
return null;
|
|
58
|
-
if (typeof v === 'string'
|
|
59
|
-
|
|
70
|
+
if (typeof v === 'string') {
|
|
71
|
+
if (isLikelyImageUrl(v))
|
|
72
|
+
return v;
|
|
73
|
+
if (isImageKeyOrAttr(key, attr) && isUrlLike(v))
|
|
74
|
+
return v;
|
|
75
|
+
}
|
|
60
76
|
if (typeof v === 'object') {
|
|
61
77
|
const u = (v.url || v.src || v.value);
|
|
62
|
-
|
|
78
|
+
if (u) {
|
|
79
|
+
if (isLikelyImageUrl(u))
|
|
80
|
+
return u;
|
|
81
|
+
if (isImageKeyOrAttr(key, attr) && isUrlLike(u))
|
|
82
|
+
return u;
|
|
83
|
+
}
|
|
63
84
|
}
|
|
64
85
|
return null;
|
|
65
86
|
}
|
|
66
|
-
function extractAllImageUrls(val) {
|
|
87
|
+
function extractAllImageUrls(val, key, attr) {
|
|
67
88
|
if (Array.isArray(val)) {
|
|
68
|
-
const list = val.map(extractUrl).filter(Boolean);
|
|
89
|
+
const list = val.map((item) => extractUrl(item, key, attr)).filter(Boolean);
|
|
69
90
|
if (list.length)
|
|
70
91
|
return list;
|
|
71
|
-
return (val.filter((s) => typeof s === 'string' && isLikelyImageUrl(s)) || []);
|
|
92
|
+
return (val.filter((s) => typeof s === 'string' && (isLikelyImageUrl(s) || (isImageKeyOrAttr(key, attr) && isUrlLike(s)))) || []);
|
|
72
93
|
}
|
|
73
|
-
const one = extractUrl(val);
|
|
94
|
+
const one = extractUrl(val, key, attr);
|
|
74
95
|
return one ? [one] : [];
|
|
75
96
|
}
|
|
76
97
|
function humanizeLabel(key) {
|
|
@@ -275,7 +296,7 @@ function renderValue(input, key, attr) {
|
|
|
275
296
|
if (localizedTime)
|
|
276
297
|
return _jsx("span", { children: localizedTime });
|
|
277
298
|
// Detect images
|
|
278
|
-
const urls = extractAllImageUrls(val);
|
|
299
|
+
const urls = extractAllImageUrls(val, key, attr);
|
|
279
300
|
if (urls.length)
|
|
280
301
|
return _jsx(ImageThumbs, { urls: urls });
|
|
281
302
|
// Primitive or simple object/array formatting via shared formatter
|
|
@@ -319,17 +340,31 @@ function ObjectView({ obj, attributes, showKey }) {
|
|
|
319
340
|
export function DynamicViewer({ schema, data }) {
|
|
320
341
|
// Use schema order if available, otherwise object keys order
|
|
321
342
|
const attributes = (schema?.attributes ?? {});
|
|
343
|
+
// Detect if this is a User-extending schema with no linked user
|
|
344
|
+
const isUserExt = schema?.extends?.toLowerCase() === 'users';
|
|
345
|
+
const hasLinkedUser = !!(data?.exId || data?.baseId);
|
|
322
346
|
const keys = React.useMemo(() => {
|
|
323
|
-
const
|
|
347
|
+
const attrKeys = Object.keys(attributes).filter((k) => {
|
|
324
348
|
if (HIDDEN_KEYS.has(k) || isPasswordKeyRaw(k))
|
|
325
349
|
return false;
|
|
326
350
|
if (isPasswordByAttr(attributes[k]))
|
|
327
351
|
return false;
|
|
328
|
-
|
|
352
|
+
if (isPrivate(attributes[k]))
|
|
353
|
+
return false;
|
|
354
|
+
// Hide inherited fields when no base user is linked
|
|
355
|
+
if (isUserExt && !hasLinkedUser) {
|
|
356
|
+
const head = Array.isArray(attributes[k]) ? attributes[k][0] : attributes[k];
|
|
357
|
+
if (head?.inherited)
|
|
358
|
+
return false;
|
|
359
|
+
}
|
|
360
|
+
return true;
|
|
361
|
+
});
|
|
362
|
+
const extraDataKeys = Object.keys(data || {}).filter((k) => {
|
|
363
|
+
if (HIDDEN_KEYS.has(k) || isPasswordKeyRaw(k))
|
|
364
|
+
return false;
|
|
365
|
+
return !attrKeys.includes(k);
|
|
329
366
|
});
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
return Object.keys(data || {}).filter((k) => !HIDDEN_KEYS.has(k) && !isPasswordKeyRaw(k));
|
|
333
|
-
}, [attributes, data]);
|
|
367
|
+
return [...attrKeys, ...extraDataKeys];
|
|
368
|
+
}, [attributes, data, isUserExt, hasLinkedUser]);
|
|
334
369
|
return (_jsx("div", { className: "grid gap-1", children: keys.map((key) => (_jsx(FieldRow, { label: humanizeLabel(key), children: renderValue(data?.[key], key, attributes[key]) }, key))) }));
|
|
335
370
|
}
|
|
@@ -1,8 +1,48 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import React from 'react';
|
|
4
|
-
import { Drawer, DrawerBody, DrawerContent, DrawerFooter, DrawerHeader, Button, } from '@heroui/react';
|
|
4
|
+
import { Drawer, DrawerBody, DrawerContent, DrawerFooter, DrawerHeader, Button, Spinner, } from '@heroui/react';
|
|
5
|
+
import { api } from '../../lib/api';
|
|
5
6
|
import { DynamicViewer } from './DynamicViewer';
|
|
7
|
+
function ViewDrawerContent({ model, schema, data: initialData, }) {
|
|
8
|
+
const [data, setData] = React.useState(initialData);
|
|
9
|
+
const [loading, setLoading] = React.useState(false);
|
|
10
|
+
React.useEffect(() => {
|
|
11
|
+
setData(initialData);
|
|
12
|
+
const id = initialData?.id || initialData?._id;
|
|
13
|
+
if (!model || !id)
|
|
14
|
+
return;
|
|
15
|
+
let cancelled = false;
|
|
16
|
+
setLoading(true);
|
|
17
|
+
api
|
|
18
|
+
.get(model, String(id))
|
|
19
|
+
.then((res) => {
|
|
20
|
+
if (cancelled)
|
|
21
|
+
return;
|
|
22
|
+
const fetched = res?.data || res;
|
|
23
|
+
if (fetched && typeof fetched === 'object') {
|
|
24
|
+
let merged = { ...initialData, ...fetched };
|
|
25
|
+
const baseObj = fetched?.baseId;
|
|
26
|
+
if (baseObj && typeof baseObj === 'object') {
|
|
27
|
+
merged = { ...baseObj, ...merged };
|
|
28
|
+
delete merged.baseId;
|
|
29
|
+
}
|
|
30
|
+
setData(merged);
|
|
31
|
+
}
|
|
32
|
+
})
|
|
33
|
+
.catch((err) => {
|
|
34
|
+
console.error('Failed to fetch full record for view drawer:', err);
|
|
35
|
+
})
|
|
36
|
+
.finally(() => {
|
|
37
|
+
if (!cancelled)
|
|
38
|
+
setLoading(false);
|
|
39
|
+
});
|
|
40
|
+
return () => {
|
|
41
|
+
cancelled = true;
|
|
42
|
+
};
|
|
43
|
+
}, [model, initialData?.id, initialData?._id]);
|
|
44
|
+
return (_jsxs("div", { className: "relative", children: [loading && (_jsxs("div", { className: "mb-3 flex items-center gap-2 text-xs text-primary font-medium", children: [_jsx(Spinner, { size: "sm" }), " Loading full details..."] })), _jsx(DynamicViewer, { schema: schema, data: data })] }));
|
|
45
|
+
}
|
|
6
46
|
export function useViewDrawer() {
|
|
7
47
|
const [state, setState] = React.useState(null);
|
|
8
48
|
const open = React.useCallback((params) => {
|
|
@@ -18,7 +58,7 @@ export function useViewDrawer() {
|
|
|
18
58
|
return null;
|
|
19
59
|
const { isOpen, title, model, schema, data } = state;
|
|
20
60
|
const headerTitle = title || model || 'Details';
|
|
21
|
-
return (_jsx(Drawer, { isOpen: isOpen, onClose: close, placement: "right", size: "lg", children: _jsxs(DrawerContent, { className: "w-[100vw] md:max-w-[50vw]", children: [_jsx(DrawerHeader, { className: "border-b border-default-200", children: headerTitle }), _jsx(DrawerBody, { className: "px-4 py-3", children: _jsx(
|
|
61
|
+
return (_jsx(Drawer, { isOpen: isOpen, onClose: close, placement: "right", size: "lg", children: _jsxs(DrawerContent, { className: "w-[100vw] md:max-w-[50vw]", children: [_jsx(DrawerHeader, { className: "border-b border-default-200", children: headerTitle }), _jsx(DrawerBody, { className: "px-4 py-3 overflow-y-auto", children: _jsx(ViewDrawerContent, { model: model, schema: schema, data: data }) }), _jsx(DrawerFooter, { className: "border-t border-default-200", children: _jsx(Button, { variant: "flat", onPress: close, children: "Close" }) })] }) }));
|
|
22
62
|
}, [state, close]);
|
|
23
63
|
return { open, close, ViewDrawerPortal };
|
|
24
64
|
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { useCallback } from 'react';
|
|
3
|
+
import { useRouter } from 'next/navigation';
|
|
4
|
+
import { useNextMin } from '../providers/NextMinProvider';
|
|
5
|
+
export function useAppNavigation() {
|
|
6
|
+
const { navigate } = useNextMin();
|
|
7
|
+
const router = useRouter();
|
|
8
|
+
return useCallback((url, opts) => {
|
|
9
|
+
if (navigate) {
|
|
10
|
+
navigate(url);
|
|
11
|
+
}
|
|
12
|
+
else if (router) {
|
|
13
|
+
if (opts?.replace && typeof router.replace === 'function') {
|
|
14
|
+
router.replace(url);
|
|
15
|
+
}
|
|
16
|
+
else if (typeof router.push === 'function') {
|
|
17
|
+
router.push(url);
|
|
18
|
+
}
|
|
19
|
+
else if (typeof window !== 'undefined') {
|
|
20
|
+
if (opts?.replace) {
|
|
21
|
+
window.location.replace(url);
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
window.location.href = url;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
else if (typeof window !== 'undefined') {
|
|
29
|
+
if (opts?.replace) {
|
|
30
|
+
window.location.replace(url);
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
window.location.href = url;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}, [navigate, router]);
|
|
37
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -2,7 +2,9 @@ export * from './providers/NextMinProvider';
|
|
|
2
2
|
export * from './components/AdminApp';
|
|
3
3
|
export * from './components/ArchitectureDemo';
|
|
4
4
|
export * from './hooks/useRealtime';
|
|
5
|
+
export * from './hooks/useAppNavigation';
|
|
5
6
|
export * from './lib/QueryBuilder';
|
|
6
7
|
export * from './lib/AuthClient';
|
|
7
8
|
export * from './lib/RealtimeClient';
|
|
8
9
|
export * from './lib/api';
|
|
10
|
+
export type { ThemeConfig } from './providers/themeResolver';
|
package/dist/index.js
CHANGED
|
@@ -2,6 +2,7 @@ export * from './providers/NextMinProvider';
|
|
|
2
2
|
export * from './components/AdminApp';
|
|
3
3
|
export * from './components/ArchitectureDemo';
|
|
4
4
|
export * from './hooks/useRealtime';
|
|
5
|
+
export * from './hooks/useAppNavigation';
|
|
5
6
|
export * from './lib/QueryBuilder';
|
|
6
7
|
export * from './lib/AuthClient';
|
|
7
8
|
export * from './lib/RealtimeClient';
|
|
@@ -10,7 +10,8 @@ export interface QueryOptions {
|
|
|
10
10
|
sortType?: string;
|
|
11
11
|
page?: number;
|
|
12
12
|
limit?: number;
|
|
13
|
-
fields?: string[];
|
|
13
|
+
fields?: string | string[];
|
|
14
|
+
select?: string | string[];
|
|
14
15
|
q?: string;
|
|
15
16
|
searchKeys?: string;
|
|
16
17
|
}
|
|
@@ -21,6 +22,7 @@ export declare class QueryBuilder {
|
|
|
21
22
|
private options;
|
|
22
23
|
where(field: string, operator: FilterOperator, value: any): QueryBuilder;
|
|
23
24
|
orWhere(field: string, operator: FilterOperator, value: any): QueryBuilder;
|
|
25
|
+
select(fields: string | string[]): QueryBuilder;
|
|
24
26
|
sort(field: string, direction?: 'asc' | 'desc'): QueryBuilder;
|
|
25
27
|
page(page: number, limit?: number): QueryBuilder;
|
|
26
28
|
limit(limit: number): QueryBuilder;
|
package/dist/lib/QueryBuilder.js
CHANGED
|
@@ -33,6 +33,10 @@ export class QueryBuilder {
|
|
|
33
33
|
this.options.where = { $or: [currentWhere, newCondition] };
|
|
34
34
|
return this;
|
|
35
35
|
}
|
|
36
|
+
select(fields) {
|
|
37
|
+
this.options.select = fields;
|
|
38
|
+
return this;
|
|
39
|
+
}
|
|
36
40
|
sort(field, direction = 'asc') {
|
|
37
41
|
this.options.sort = field;
|
|
38
42
|
this.options.sortType = direction;
|
|
@@ -69,6 +73,12 @@ export class QueryBuilder {
|
|
|
69
73
|
params.q = this.options.q;
|
|
70
74
|
if (this.options.searchKeys)
|
|
71
75
|
params.searchKeys = this.options.searchKeys;
|
|
76
|
+
const sel = this.options.select ?? this.options.fields;
|
|
77
|
+
if (sel) {
|
|
78
|
+
const strSel = Array.isArray(sel) ? sel.join(',') : String(sel);
|
|
79
|
+
if (strSel.trim())
|
|
80
|
+
params.select = strSel.trim();
|
|
81
|
+
}
|
|
72
82
|
return params;
|
|
73
83
|
}
|
|
74
84
|
}
|
package/dist/lib/api.d.ts
CHANGED
|
@@ -9,6 +9,7 @@ export declare class ApiError extends Error {
|
|
|
9
9
|
export declare class NextMinClient {
|
|
10
10
|
private baseURL;
|
|
11
11
|
private apiKey;
|
|
12
|
+
private static inFlightRequests;
|
|
12
13
|
constructor(options?: {
|
|
13
14
|
baseURL?: string;
|
|
14
15
|
apiKey?: string;
|
|
@@ -30,6 +31,11 @@ export type ListParams = {
|
|
|
30
31
|
dateFrom?: string;
|
|
31
32
|
dateTo?: string;
|
|
32
33
|
dateKey?: string;
|
|
34
|
+
sort?: string;
|
|
35
|
+
sortType?: string;
|
|
36
|
+
where?: Record<string, any>;
|
|
37
|
+
select?: string | string[];
|
|
38
|
+
fields?: string | string[];
|
|
33
39
|
};
|
|
34
40
|
export declare const api: {
|
|
35
41
|
getSchemas: () => Promise<unknown>;
|
package/dist/lib/api.js
CHANGED
|
@@ -25,39 +25,56 @@ export class NextMinClient {
|
|
|
25
25
|
headers.Authorization = `Bearer ${bearer}`;
|
|
26
26
|
const queryString = query ? qs(query) : '';
|
|
27
27
|
const url = `${this.baseURL || normalizeBase(AuthClient.getBaseURL())}${path}${queryString}`;
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
body: body && json && !isForm ? JSON.stringify(body) : body,
|
|
34
|
-
});
|
|
35
|
-
}
|
|
36
|
-
catch (networkErr) {
|
|
37
|
-
const err = new ApiError(networkErr?.message || 'Network error', 0);
|
|
38
|
-
err.request = { url, method, headers };
|
|
39
|
-
throw err;
|
|
40
|
-
}
|
|
41
|
-
const raw = await res.text();
|
|
42
|
-
let payload = {};
|
|
43
|
-
try {
|
|
44
|
-
payload = raw ? JSON.parse(raw) : {};
|
|
45
|
-
}
|
|
46
|
-
catch {
|
|
47
|
-
payload = raw || {};
|
|
28
|
+
if (method === 'GET') {
|
|
29
|
+
const pending = NextMinClient.inFlightRequests.get(url);
|
|
30
|
+
if (pending) {
|
|
31
|
+
return pending;
|
|
32
|
+
}
|
|
48
33
|
}
|
|
49
|
-
|
|
50
|
-
|
|
34
|
+
const execRequest = (async () => {
|
|
35
|
+
let res;
|
|
36
|
+
try {
|
|
37
|
+
res = await fetch(url, {
|
|
38
|
+
method,
|
|
39
|
+
headers,
|
|
40
|
+
body: body && json && !isForm ? JSON.stringify(body) : body,
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
catch (networkErr) {
|
|
44
|
+
const err = new ApiError(networkErr?.message || 'Network error', 0);
|
|
45
|
+
err.request = { url, method, headers };
|
|
46
|
+
throw err;
|
|
47
|
+
}
|
|
48
|
+
const raw = await res.text();
|
|
49
|
+
let payload = {};
|
|
50
|
+
try {
|
|
51
|
+
payload = raw ? JSON.parse(raw) : {};
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
payload = raw || {};
|
|
55
|
+
}
|
|
56
|
+
if (!res.ok) {
|
|
57
|
+
const msg = payload?.message || payload?.error || res.statusText || 'API error';
|
|
58
|
+
throw new ApiError(typeof msg === 'string' ? msg : 'API error', res.status, payload);
|
|
59
|
+
}
|
|
60
|
+
return payload;
|
|
61
|
+
})();
|
|
62
|
+
if (method === 'GET') {
|
|
63
|
+
NextMinClient.inFlightRequests.set(url, execRequest);
|
|
64
|
+
execRequest.finally(() => {
|
|
65
|
+
NextMinClient.inFlightRequests.delete(url);
|
|
66
|
+
});
|
|
51
67
|
}
|
|
52
|
-
return
|
|
68
|
+
return execRequest;
|
|
53
69
|
}
|
|
54
70
|
}
|
|
71
|
+
NextMinClient.inFlightRequests = new Map();
|
|
55
72
|
// Keep the global request for backward compatibility, but make it use AuthClient config
|
|
56
73
|
async function request(path, opts = {}) {
|
|
57
74
|
return new NextMinClient().request(path, opts);
|
|
58
75
|
}
|
|
59
76
|
function modelPath(modelName) {
|
|
60
|
-
return
|
|
77
|
+
return `/${modelName}`;
|
|
61
78
|
}
|
|
62
79
|
function qs(params) {
|
|
63
80
|
const sp = new URLSearchParams();
|
|
@@ -95,6 +112,10 @@ export const api = {
|
|
|
95
112
|
builder.page(Math.max(1, options.page), options.limit);
|
|
96
113
|
if (options.q)
|
|
97
114
|
builder.search(options.q, options.searchKeys?.split(','));
|
|
115
|
+
if (options.select)
|
|
116
|
+
builder.select(options.select);
|
|
117
|
+
else if (options.fields)
|
|
118
|
+
builder.select(options.fields);
|
|
98
119
|
const params = builder.build();
|
|
99
120
|
return request(`${modelPath(modelName)}${qs(params)}`);
|
|
100
121
|
},
|
package/dist/lib/schemaUtils.js
CHANGED
package/dist/lib/types.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ export type AttributeBase = {
|
|
|
5
5
|
private?: boolean;
|
|
6
6
|
ref?: string;
|
|
7
7
|
longtext?: boolean;
|
|
8
|
+
format?: string;
|
|
8
9
|
show?: string;
|
|
9
10
|
populateSlugFrom?: string;
|
|
10
11
|
minLength?: number;
|
|
@@ -17,7 +18,11 @@ export type ArrayAttribute = AttributeBase[];
|
|
|
17
18
|
export type Attribute = AttributeBase | ArrayAttribute;
|
|
18
19
|
export type CustomAction = {
|
|
19
20
|
label: string;
|
|
20
|
-
|
|
21
|
+
url?: string;
|
|
22
|
+
href?: string;
|
|
23
|
+
type?: string;
|
|
24
|
+
actionType?: string;
|
|
25
|
+
urlTemplate?: string;
|
|
21
26
|
icon?: string;
|
|
22
27
|
variant?: 'light' | 'flat' | 'solid' | 'bordered';
|
|
23
28
|
color?: 'default' | 'primary' | 'secondary' | 'success' | 'warning' | 'danger';
|