@nocobase/portal-template-default 1.0.3 → 1.0.5
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 +10 -5
- package/package.json +17 -17
- package/registry/nocobase-ai/components/chat/chat-message.tsx +8 -6
- package/registry/nocobase-ai/components/chat/chat-messages.tsx +8 -3
- package/registry/nocobase-ai/components/chat/markdown-message.tsx +26 -0
- package/registry/nocobase-ai/components/chat/sub-agent-conversation.tsx +3 -3
- package/registry/nocobase-ai/demo/configuration-gate.tsx +83 -35
- package/registry/nocobase-i18n/README.md +23 -32
- package/registry/nocobase-i18n/components/language-switcher.tsx +1 -1
- package/registry/nocobase-i18n/demo/index.tsx +2 -2
- package/registry/nocobase-i18n/demo/prompt-generator.tsx +3 -2
- package/registry/nocobase-i18n/extension.tsx +3 -4
- package/registry/nocobase-i18n/index.ts +4 -3
- package/registry/nocobase-i18n/locales/en-US.ts +2 -52
- package/registry/nocobase-i18n/locales/index.ts +8 -0
- package/registry/nocobase-i18n/locales/zh-CN.ts +2 -52
- package/registry/nocobase-i18n/provider.tsx +18 -6
- package/registry/nocobase-i18n/server-resources.ts +13 -31
- package/registry/nocobase-i18n/tests/i18n-regression.mjs +14 -5
- package/registry/nocobase-route-surfaces/README.md +21 -0
- package/registry/nocobase-route-surfaces/demo/index.tsx +266 -0
- package/{src/extensions/nocobase-acl → registry/nocobase-route-surfaces}/demo/lazy-route.tsx +2 -1
- package/registry/nocobase-route-surfaces/demo/prompt-generator.tsx +90 -0
- package/registry/nocobase-route-surfaces/demo/scenarios.ts +95 -0
- package/registry/nocobase-route-surfaces/extension.tsx +119 -0
- package/registry/nocobase-route-surfaces/index.ts +7 -0
- package/registry/nocobase-route-surfaces/route-dialog.tsx +145 -0
- package/registry/nocobase-route-surfaces/route-drawer.tsx +186 -0
- package/registry/nocobase-route-surfaces/route-page.tsx +29 -0
- package/registry/nocobase-route-surfaces/route-surface-context.ts +13 -0
- package/registry/nocobase-route-surfaces/use-refine-unsaved-changes.tsx +106 -0
- package/registry/nocobase-route-surfaces/use-route-surface-close.ts +15 -0
- package/registry/nocobase-route-surfaces/use-route-surface-state.ts +65 -0
- package/registry.config.json +30 -3
- package/scripts/registry.mjs +0 -2
- package/src/App.tsx +65 -35
- package/src/app/extension.ts +1 -2
- package/src/app/extensions.tsx +0 -14
- package/src/components/app-shell/breadcrumb.tsx +67 -2
- package/src/components/app-shell/sidebar.tsx +2 -2
- package/src/components/resources/views/list-view.tsx +3 -2
- package/src/components/ui/sheet.tsx +22 -21
- package/src/lib/nocobase/client.ts +6 -1
- package/src/locales/en-US.ts +81 -0
- package/src/locales/zh-CN.ts +77 -0
- package/src/pages/users/create.tsx +76 -53
- package/src/pages/users/edit.tsx +87 -56
- package/src/pages/users/index.ts +3 -0
- package/src/pages/users/layout.tsx +15 -0
- package/src/pages/users/list.tsx +19 -21
- package/src/pages/users/role-badges.tsx +43 -0
- package/src/pages/users/role-detail.tsx +272 -0
- package/src/pages/users/role-utils.ts +6 -0
- package/src/pages/users/routes.ts +24 -0
- package/src/pages/users/show.tsx +195 -104
- package/src/pages/users/types.ts +13 -0
- package/src/providers/i18n/index.ts +3 -0
- package/{registry/nocobase-i18n → src/providers/i18n}/runtime.ts +63 -16
- package/src/providers/system-settings/context.ts +26 -0
- package/src/providers/system-settings/index.ts +2 -0
- package/src/providers/system-settings/provider.tsx +94 -0
- package/src/extensions/nocobase-acl/README.md +0 -19
- package/src/extensions/nocobase-acl/components/acl-boundary.tsx +0 -106
- package/src/extensions/nocobase-acl/components/index.ts +0 -3
- package/src/extensions/nocobase-acl/components/role-options.ts +0 -36
- package/src/extensions/nocobase-acl/components/role-switcher.tsx +0 -135
- package/src/extensions/nocobase-acl/demo/boundary-api.tsx +0 -186
- package/src/extensions/nocobase-acl/demo/components.tsx +0 -231
- package/src/extensions/nocobase-acl/demo/index.tsx +0 -505
- package/src/extensions/nocobase-acl/demo/prompt-generator.tsx +0 -134
- package/src/extensions/nocobase-acl/demo/role-switcher-prompt-generator.tsx +0 -198
- package/src/extensions/nocobase-acl/demo/scenario-section.tsx +0 -37
- package/src/extensions/nocobase-acl/extension.tsx +0 -71
- package/src/extensions/nocobase-acl/index.ts +0 -1
- package/src/extensions/nocobase-ai/README.md +0 -70
- package/src/extensions/nocobase-ai/adapters/react-hook-form.ts +0 -20
- package/src/extensions/nocobase-ai/assets/nocobase-ai-chat.svg +0 -50
- package/src/extensions/nocobase-ai/components/ai-root-provider.tsx +0 -32
- package/src/extensions/nocobase-ai/components/chat/ai-employee-avatar.tsx +0 -31
- package/src/extensions/nocobase-ai/components/chat/chat-attachment.tsx +0 -92
- package/src/extensions/nocobase-ai/components/chat/chat-compact.tsx +0 -90
- package/src/extensions/nocobase-ai/components/chat/chat-composer.tsx +0 -429
- package/src/extensions/nocobase-ai/components/chat/chat-empty-state.tsx +0 -51
- package/src/extensions/nocobase-ai/components/chat/chat-header.tsx +0 -88
- package/src/extensions/nocobase-ai/components/chat/chat-history-dialog.tsx +0 -77
- package/src/extensions/nocobase-ai/components/chat/chat-message.tsx +0 -252
- package/src/extensions/nocobase-ai/components/chat/chat-messages.tsx +0 -173
- package/src/extensions/nocobase-ai/components/chat/chat-window.tsx +0 -118
- package/src/extensions/nocobase-ai/components/chat/conversation-list.tsx +0 -343
- package/src/extensions/nocobase-ai/components/chat/markdown-message.tsx +0 -165
- package/src/extensions/nocobase-ai/components/chat/model-select-options.tsx +0 -30
- package/src/extensions/nocobase-ai/components/chat/reasoning-panel.tsx +0 -29
- package/src/extensions/nocobase-ai/components/chat/sub-agent-conversation.tsx +0 -142
- package/src/extensions/nocobase-ai/components/chat/tool-call-card.tsx +0 -316
- package/src/extensions/nocobase-ai/components/chat/user-prompt-editor.tsx +0 -99
- package/src/extensions/nocobase-ai/components/chat/work-context-chip.tsx +0 -114
- package/src/extensions/nocobase-ai/components/index.ts +0 -59
- package/src/extensions/nocobase-ai/components/page-elements/ai-form.tsx +0 -44
- package/src/extensions/nocobase-ai/components/page-elements/page-element-provider.tsx +0 -469
- package/src/extensions/nocobase-ai/components/surfaces/chat-dialog.tsx +0 -31
- package/src/extensions/nocobase-ai/components/surfaces/chat-inline.tsx +0 -18
- package/src/extensions/nocobase-ai/components/surfaces/chat-page.tsx +0 -18
- package/src/extensions/nocobase-ai/components/surfaces/chat-side-panel-layout.tsx +0 -54
- package/src/extensions/nocobase-ai/components/surfaces/chat-side-panel.tsx +0 -74
- package/src/extensions/nocobase-ai/components/surfaces/chat-surface-actions.tsx +0 -54
- package/src/extensions/nocobase-ai/components/tools/builtin-tool-renderers.tsx +0 -31
- package/src/extensions/nocobase-ai/components/tools/business-report-dialog.tsx +0 -364
- package/src/extensions/nocobase-ai/components/tools/business-report-renderer.tsx +0 -122
- package/src/extensions/nocobase-ai/components/tools/business-report-utils.tsx +0 -282
- package/src/extensions/nocobase-ai/components/tools/chart-renderer.tsx +0 -99
- package/src/extensions/nocobase-ai/components/tools/echarts-preview.tsx +0 -59
- package/src/extensions/nocobase-ai/components/tools/echarts-runtime-advanced.ts +0 -18
- package/src/extensions/nocobase-ai/components/tools/echarts-runtime-common.ts +0 -20
- package/src/extensions/nocobase-ai/components/tools/echarts-runtime-components.ts +0 -34
- package/src/extensions/nocobase-ai/components/tools/echarts-runtime-hierarchy.ts +0 -24
- package/src/extensions/nocobase-ai/components/tools/echarts-runtime.ts +0 -138
- package/src/extensions/nocobase-ai/components/tools/sub-agent-renderer.tsx +0 -67
- package/src/extensions/nocobase-ai/components/tools/suggestions-renderer.tsx +0 -59
- package/src/extensions/nocobase-ai/components/tools/tool-renderer-provider.tsx +0 -55
- package/src/extensions/nocobase-ai/components/tools/tool-renderer-utils.ts +0 -18
- package/src/extensions/nocobase-ai/components/tools/workflow-renderer.tsx +0 -108
- package/src/extensions/nocobase-ai/components/triggers/ai-chat-floating-trigger.tsx +0 -73
- package/src/extensions/nocobase-ai/components/triggers/ai-employee-shortcut.tsx +0 -149
- package/src/extensions/nocobase-ai/demo/configuration-gate.tsx +0 -49
- package/src/extensions/nocobase-ai/demo/container-showcase.tsx +0 -210
- package/src/extensions/nocobase-ai/demo/floating.tsx +0 -134
- package/src/extensions/nocobase-ai/demo/index.tsx +0 -346
- package/src/extensions/nocobase-ai/demo/interaction-showcase.tsx +0 -151
- package/src/extensions/nocobase-ai/demo/page-context-prompt-generator.tsx +0 -355
- package/src/extensions/nocobase-ai/demo/page-context.tsx +0 -884
- package/src/extensions/nocobase-ai/demo/page-element-showcase.tsx +0 -197
- package/src/extensions/nocobase-ai/demo/prompt-card.tsx +0 -41
- package/src/extensions/nocobase-ai/demo/prompt-generator.tsx +0 -452
- package/src/extensions/nocobase-ai/demo/shortcut.tsx +0 -1207
- package/src/extensions/nocobase-ai/demo/tool-cards.tsx +0 -530
- package/src/extensions/nocobase-ai/extension.tsx +0 -158
- package/src/extensions/nocobase-ai/global-ai-chat.tsx +0 -213
- package/src/extensions/nocobase-ai/index.ts +0 -3
- package/src/extensions/nocobase-ai/providers/ai-provider.tsx +0 -323
- package/src/extensions/nocobase-ai/providers/avatars.ts +0 -817
- package/src/extensions/nocobase-ai/providers/chat-context.tsx +0 -109
- package/src/extensions/nocobase-ai/providers/chat-controller.ts +0 -84
- package/src/extensions/nocobase-ai/providers/chat-message-utils.ts +0 -198
- package/src/extensions/nocobase-ai/providers/chat-provider.tsx +0 -871
- package/src/extensions/nocobase-ai/providers/chat-reducer.ts +0 -147
- package/src/extensions/nocobase-ai/providers/chat-task-utils.ts +0 -105
- package/src/extensions/nocobase-ai/providers/chat-transport.ts +0 -184
- package/src/extensions/nocobase-ai/providers/form-registry.tsx +0 -271
- package/src/extensions/nocobase-ai/providers/frontend-tool-registry.tsx +0 -231
- package/src/extensions/nocobase-ai/providers/index.ts +0 -61
- package/src/extensions/nocobase-ai/providers/model.ts +0 -32
- package/src/extensions/nocobase-ai/providers/page-context.tsx +0 -136
- package/src/extensions/nocobase-ai/providers/stream-coalescer.ts +0 -60
- package/src/extensions/nocobase-ai/providers/stream-event-utils.ts +0 -82
- package/src/extensions/nocobase-ai/providers/stream-parser.ts +0 -61
- package/src/extensions/nocobase-ai/providers/sub-agent-stream.ts +0 -316
- package/src/extensions/nocobase-ai/providers/types.ts +0 -183
- package/src/extensions/nocobase-ai/providers/ui-message-stream.ts +0 -463
- package/src/extensions/nocobase-ai/providers/use-automatic-tool-approval.ts +0 -94
- package/src/extensions/nocobase-ai/providers/use-chat-attachments.ts +0 -206
- package/src/extensions/nocobase-ai/providers/use-chat-message-actions.ts +0 -554
- package/src/extensions/nocobase-ai/providers/use-chat-runtime.ts +0 -210
- package/src/extensions/nocobase-ai/providers/use-chat-work-context.ts +0 -93
- package/src/extensions/nocobase-ai/providers/use-conversation-catalog.ts +0 -108
- package/src/extensions/nocobase-ai/providers/use-conversation-history.ts +0 -178
- package/src/extensions/nocobase-ai/services/index.ts +0 -7
- package/src/extensions/nocobase-ai/services/nocobase-ai-service.ts +0 -454
- package/src/extensions/nocobase-ai/services/types.ts +0 -82
- /package/{registry/nocobase-i18n → src/providers/i18n}/i18n-provider.ts +0 -0
- /package/{registry/nocobase-i18n → src/providers/i18n}/locale-store.ts +0 -0
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { useTranslate, useWarnAboutChange } from "@refinedev/core";
|
|
2
|
+
import { TriangleAlert } from "lucide-react";
|
|
3
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
AlertDialog,
|
|
7
|
+
AlertDialogAction,
|
|
8
|
+
AlertDialogCancel,
|
|
9
|
+
AlertDialogContent,
|
|
10
|
+
AlertDialogDescription,
|
|
11
|
+
AlertDialogFooter,
|
|
12
|
+
AlertDialogHeader,
|
|
13
|
+
AlertDialogMedia,
|
|
14
|
+
AlertDialogTitle,
|
|
15
|
+
} from "@/components/ui/alert-dialog";
|
|
16
|
+
|
|
17
|
+
type PendingConfirmation = {
|
|
18
|
+
promise: Promise<boolean>;
|
|
19
|
+
resolve: (allowed: boolean) => void;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export function useRefineUnsavedChangesGuard() {
|
|
23
|
+
const translate = useTranslate();
|
|
24
|
+
const { warnWhen, setWarnWhen } = useWarnAboutChange();
|
|
25
|
+
const [open, setOpen] = useState(false);
|
|
26
|
+
const pendingRef = useRef<PendingConfirmation | null>(null);
|
|
27
|
+
|
|
28
|
+
const settle = useCallback(
|
|
29
|
+
(allowed: boolean) => {
|
|
30
|
+
const pending = pendingRef.current;
|
|
31
|
+
if (!pending) return;
|
|
32
|
+
|
|
33
|
+
pendingRef.current = null;
|
|
34
|
+
if (allowed) setWarnWhen(false);
|
|
35
|
+
setOpen(false);
|
|
36
|
+
pending.resolve(allowed);
|
|
37
|
+
},
|
|
38
|
+
[setWarnWhen]
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
useEffect(
|
|
42
|
+
() => () => {
|
|
43
|
+
pendingRef.current?.resolve(false);
|
|
44
|
+
pendingRef.current = null;
|
|
45
|
+
},
|
|
46
|
+
[]
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
const beforeClose = useCallback(() => {
|
|
50
|
+
if (!warnWhen) return true;
|
|
51
|
+
if (pendingRef.current) return pendingRef.current.promise;
|
|
52
|
+
|
|
53
|
+
let resolve!: (allowed: boolean) => void;
|
|
54
|
+
const promise = new Promise<boolean>((next) => {
|
|
55
|
+
resolve = next;
|
|
56
|
+
});
|
|
57
|
+
pendingRef.current = { promise, resolve };
|
|
58
|
+
setOpen(true);
|
|
59
|
+
return promise;
|
|
60
|
+
}, [warnWhen]);
|
|
61
|
+
|
|
62
|
+
const confirmation = useMemo(
|
|
63
|
+
() => (
|
|
64
|
+
<AlertDialog
|
|
65
|
+
open={open}
|
|
66
|
+
onOpenChange={(nextOpen) => {
|
|
67
|
+
if (!nextOpen) settle(false);
|
|
68
|
+
}}
|
|
69
|
+
>
|
|
70
|
+
<AlertDialogContent>
|
|
71
|
+
<AlertDialogHeader>
|
|
72
|
+
<AlertDialogMedia>
|
|
73
|
+
<TriangleAlert />
|
|
74
|
+
</AlertDialogMedia>
|
|
75
|
+
<AlertDialogTitle>
|
|
76
|
+
{translate(
|
|
77
|
+
"unsavedChanges.title",
|
|
78
|
+
"Discard unsaved changes?"
|
|
79
|
+
)}
|
|
80
|
+
</AlertDialogTitle>
|
|
81
|
+
<AlertDialogDescription>
|
|
82
|
+
{translate(
|
|
83
|
+
"unsavedChanges.description",
|
|
84
|
+
"Your changes have not been saved. Leaving now will discard them."
|
|
85
|
+
)}
|
|
86
|
+
</AlertDialogDescription>
|
|
87
|
+
</AlertDialogHeader>
|
|
88
|
+
<AlertDialogFooter>
|
|
89
|
+
<AlertDialogCancel onClick={() => settle(false)}>
|
|
90
|
+
{translate("unsavedChanges.stay", "Keep editing")}
|
|
91
|
+
</AlertDialogCancel>
|
|
92
|
+
<AlertDialogAction
|
|
93
|
+
variant="destructive"
|
|
94
|
+
onClick={() => settle(true)}
|
|
95
|
+
>
|
|
96
|
+
{translate("unsavedChanges.leave", "Discard changes")}
|
|
97
|
+
</AlertDialogAction>
|
|
98
|
+
</AlertDialogFooter>
|
|
99
|
+
</AlertDialogContent>
|
|
100
|
+
</AlertDialog>
|
|
101
|
+
),
|
|
102
|
+
[open, settle, translate]
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
return { beforeClose, confirmation };
|
|
106
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { useContext } from "react";
|
|
2
|
+
|
|
3
|
+
import { RouteSurfaceContext } from "./route-surface-context";
|
|
4
|
+
|
|
5
|
+
export function useRouteSurfaceClose() {
|
|
6
|
+
const close = useContext(RouteSurfaceContext);
|
|
7
|
+
|
|
8
|
+
if (!close) {
|
|
9
|
+
throw new Error(
|
|
10
|
+
"useRouteSurfaceClose must be used inside a RouteDrawer, RouteDialog, or RoutePage."
|
|
11
|
+
);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
return close;
|
|
15
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
2
|
+
import { useNavigate } from "react-router";
|
|
3
|
+
|
|
4
|
+
import type {
|
|
5
|
+
RouteSurfaceClose,
|
|
6
|
+
RouteSurfaceCloseOptions,
|
|
7
|
+
} from "./route-surface-context";
|
|
8
|
+
|
|
9
|
+
export type RouteSurfaceBeforeClose = () => boolean | Promise<boolean>;
|
|
10
|
+
|
|
11
|
+
export function useRouteSurfaceState({
|
|
12
|
+
closeTo,
|
|
13
|
+
beforeClose,
|
|
14
|
+
animated,
|
|
15
|
+
}: {
|
|
16
|
+
closeTo: string;
|
|
17
|
+
beforeClose?: RouteSurfaceBeforeClose;
|
|
18
|
+
animated: boolean;
|
|
19
|
+
}) {
|
|
20
|
+
const navigate = useNavigate();
|
|
21
|
+
const [open, setOpen] = useState(!animated);
|
|
22
|
+
const closingRef = useRef(false);
|
|
23
|
+
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
if (!animated) return;
|
|
26
|
+
|
|
27
|
+
const frame = requestAnimationFrame(() => setOpen(true));
|
|
28
|
+
return () => cancelAnimationFrame(frame);
|
|
29
|
+
}, [animated]);
|
|
30
|
+
|
|
31
|
+
const navigateAfterClose = useCallback(() => {
|
|
32
|
+
navigate(closeTo, { replace: true });
|
|
33
|
+
}, [closeTo, navigate]);
|
|
34
|
+
|
|
35
|
+
const close = useCallback<RouteSurfaceClose>(
|
|
36
|
+
async (options?: RouteSurfaceCloseOptions) => {
|
|
37
|
+
if (closingRef.current) return false;
|
|
38
|
+
|
|
39
|
+
closingRef.current = true;
|
|
40
|
+
try {
|
|
41
|
+
if (!options?.skipBeforeClose && beforeClose) {
|
|
42
|
+
const allowed = await beforeClose();
|
|
43
|
+
if (!allowed) return false;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (animated) {
|
|
47
|
+
setOpen(false);
|
|
48
|
+
} else {
|
|
49
|
+
navigateAfterClose();
|
|
50
|
+
}
|
|
51
|
+
return true;
|
|
52
|
+
} finally {
|
|
53
|
+
closingRef.current = false;
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
[animated, beforeClose, navigateAfterClose]
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
open,
|
|
61
|
+
setOpen,
|
|
62
|
+
close,
|
|
63
|
+
navigateAfterClose,
|
|
64
|
+
};
|
|
65
|
+
}
|
package/registry.config.json
CHANGED
|
@@ -2,14 +2,41 @@
|
|
|
2
2
|
"name": "nocobase-ai",
|
|
3
3
|
"homepage": "https://www.nocobase.com",
|
|
4
4
|
"items": [
|
|
5
|
+
{
|
|
6
|
+
"name": "route-surfaces",
|
|
7
|
+
"type": "registry:lib",
|
|
8
|
+
"title": "NocoBase Route Surfaces",
|
|
9
|
+
"description": "URL-backed drawer, dialog, and page containers with nested drawer stacking and a shared close lifecycle.",
|
|
10
|
+
"dependencies": [
|
|
11
|
+
"@base-ui/react@^1.6.0",
|
|
12
|
+
"@refinedev/core@^5.0.8",
|
|
13
|
+
"lucide-react@^0.487.0",
|
|
14
|
+
"react-router@^7.0.2"
|
|
15
|
+
],
|
|
16
|
+
"registryDependencies": [
|
|
17
|
+
"alert-dialog",
|
|
18
|
+
"badge",
|
|
19
|
+
"button",
|
|
20
|
+
"card",
|
|
21
|
+
"input",
|
|
22
|
+
"label",
|
|
23
|
+
"select",
|
|
24
|
+
"textarea"
|
|
25
|
+
],
|
|
26
|
+
"docs": "Provides presentation containers plus an integrated Demo and Prompt generator. Route definitions, ACL guards, data fetching, and business content remain application-owned.",
|
|
27
|
+
"source": {
|
|
28
|
+
"root": "registry/nocobase-route-surfaces",
|
|
29
|
+
"target": "src/extensions/nocobase-route-surfaces",
|
|
30
|
+
"include": ["."]
|
|
31
|
+
}
|
|
32
|
+
},
|
|
5
33
|
{
|
|
6
34
|
"name": "i18n",
|
|
7
35
|
"type": "registry:block",
|
|
8
36
|
"title": "NocoBase i18n",
|
|
9
|
-
"description": "Optional
|
|
37
|
+
"description": "Optional NocoBase server-language resources, user preference persistence, reusable language controls, and an integrated showcase for the Starter's built-in i18n runtime.",
|
|
10
38
|
"dependencies": [
|
|
11
39
|
"@refinedev/core@^5.0.8",
|
|
12
|
-
"i18next@^25.3.2",
|
|
13
40
|
"lucide-react@^0.487.0",
|
|
14
41
|
"react-router@^7.0.2"
|
|
15
42
|
],
|
|
@@ -23,7 +50,7 @@
|
|
|
23
50
|
"select",
|
|
24
51
|
"textarea"
|
|
25
52
|
],
|
|
26
|
-
"docs": "Requires a compatible NocoBase Starter with the
|
|
53
|
+
"docs": "Requires a compatible NocoBase Starter with the built-in i18n provider, shared System Settings bootstrap, and AppExtension Provider/UserMenuItems host fields. The Registry reuses systemSettings:get data and calls app:getLang only for registered dynamic namespaces such as lm-collections.",
|
|
27
54
|
"source": {
|
|
28
55
|
"root": "registry/nocobase-i18n",
|
|
29
56
|
"target": "src/extensions/nocobase-i18n",
|
package/scripts/registry.mjs
CHANGED
|
@@ -2,8 +2,6 @@ import fs from "node:fs";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
4
|
|
|
5
|
-
console.log(`Running registry script with action: ${process.argv[2]}`);
|
|
6
|
-
|
|
7
5
|
const projectRoot = path.resolve(
|
|
8
6
|
path.dirname(fileURLToPath(import.meta.url)),
|
|
9
7
|
".."
|
package/src/App.tsx
CHANGED
|
@@ -5,7 +5,14 @@ import routerProvider, {
|
|
|
5
5
|
CatchAllNavigate,
|
|
6
6
|
UnsavedChangesNotifier,
|
|
7
7
|
} from "@refinedev/react-router";
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
UserCreate,
|
|
10
|
+
UserEdit,
|
|
11
|
+
UserResourceLayout,
|
|
12
|
+
RoleDetailRoute,
|
|
13
|
+
userRoutes,
|
|
14
|
+
UserShow,
|
|
15
|
+
} from "./pages/users";
|
|
9
16
|
import { dataProvider } from "./providers/data";
|
|
10
17
|
import { Login } from "./pages/login";
|
|
11
18
|
import { Register } from "./pages/register";
|
|
@@ -20,7 +27,6 @@ import { TooltipProvider } from "./components/ui/tooltip";
|
|
|
20
27
|
import { BrandLogo } from "./components/app-shell/brand";
|
|
21
28
|
import {
|
|
22
29
|
AppExtensionProviders,
|
|
23
|
-
extensionI18nProvider,
|
|
24
30
|
extensionResources,
|
|
25
31
|
extensionRouteElements,
|
|
26
32
|
} from "./app/extensions";
|
|
@@ -31,14 +37,16 @@ import { AclBootstrap } from "./components/access-control/acl-bootstrap";
|
|
|
31
37
|
import { ResourceAccessGuard } from "./components/access-control/resource-access-guard";
|
|
32
38
|
import { NavigateToAccessibleResource } from "./components/access-control/navigate-to-accessible-resource";
|
|
33
39
|
import { UsersRound } from "lucide-react";
|
|
40
|
+
import { i18nProvider } from "./providers/i18n";
|
|
41
|
+
import { SystemSettingsProvider } from "./providers/system-settings";
|
|
34
42
|
|
|
35
43
|
const coreResources: ResourceProps[] = [
|
|
36
44
|
{
|
|
37
45
|
name: "users",
|
|
38
|
-
list:
|
|
39
|
-
create:
|
|
40
|
-
edit:
|
|
41
|
-
show:
|
|
46
|
+
list: userRoutes.list,
|
|
47
|
+
create: userRoutes.create,
|
|
48
|
+
edit: userRoutes.edit,
|
|
49
|
+
show: userRoutes.show,
|
|
42
50
|
meta: {
|
|
43
51
|
label: "Users",
|
|
44
52
|
singularLabel: "User",
|
|
@@ -65,24 +73,25 @@ function App() {
|
|
|
65
73
|
<BrowserRouter basename={basename || undefined}>
|
|
66
74
|
<ThemeProvider>
|
|
67
75
|
<TooltipProvider>
|
|
68
|
-
<
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
76
|
+
<SystemSettingsProvider>
|
|
77
|
+
<Refine
|
|
78
|
+
dataProvider={dataProvider}
|
|
79
|
+
notificationProvider={useNotificationProvider()}
|
|
80
|
+
routerProvider={routerProvider}
|
|
81
|
+
authProvider={authProvider}
|
|
82
|
+
accessControlProvider={accessControlProvider}
|
|
83
|
+
i18nProvider={i18nProvider}
|
|
84
|
+
resources={[...coreResources, ...extensionResources]}
|
|
85
|
+
options={{
|
|
86
|
+
syncWithLocation: true,
|
|
87
|
+
warnWhenUnsavedChanges: true,
|
|
88
|
+
disableTelemetry: true,
|
|
89
|
+
title: {
|
|
90
|
+
text: "NocoBase",
|
|
91
|
+
icon: <BrandLogo className="size-14 rounded-2xl" />,
|
|
92
|
+
},
|
|
93
|
+
}}
|
|
94
|
+
>
|
|
86
95
|
<Routes>
|
|
87
96
|
<Route
|
|
88
97
|
element={
|
|
@@ -101,15 +110,10 @@ function App() {
|
|
|
101
110
|
}
|
|
102
111
|
>
|
|
103
112
|
<Route index element={<NavigateToAccessibleResource />} />
|
|
104
|
-
<Route
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
<ResourceAccessGuard resource="users" action="list">
|
|
109
|
-
<UserList />
|
|
110
|
-
</ResourceAccessGuard>
|
|
111
|
-
}
|
|
112
|
-
/>
|
|
113
|
+
<Route
|
|
114
|
+
path="/users"
|
|
115
|
+
element={<UserResourceLayout />}
|
|
116
|
+
>
|
|
113
117
|
<Route
|
|
114
118
|
path="create"
|
|
115
119
|
element={
|
|
@@ -126,6 +130,14 @@ function App() {
|
|
|
126
130
|
</ResourceAccessGuard>
|
|
127
131
|
}
|
|
128
132
|
/>
|
|
133
|
+
<Route
|
|
134
|
+
path="roles/:roleName"
|
|
135
|
+
element={
|
|
136
|
+
<ResourceAccessGuard resource="roles" action="show">
|
|
137
|
+
<RoleDetailRoute returnTo="list" />
|
|
138
|
+
</ResourceAccessGuard>
|
|
139
|
+
}
|
|
140
|
+
/>
|
|
129
141
|
<Route
|
|
130
142
|
path="show/:id"
|
|
131
143
|
element={
|
|
@@ -133,7 +145,24 @@ function App() {
|
|
|
133
145
|
<UserShow />
|
|
134
146
|
</ResourceAccessGuard>
|
|
135
147
|
}
|
|
136
|
-
|
|
148
|
+
>
|
|
149
|
+
<Route
|
|
150
|
+
path="edit"
|
|
151
|
+
element={
|
|
152
|
+
<ResourceAccessGuard resource="users" action="edit">
|
|
153
|
+
<UserEdit returnTo="show" />
|
|
154
|
+
</ResourceAccessGuard>
|
|
155
|
+
}
|
|
156
|
+
/>
|
|
157
|
+
<Route
|
|
158
|
+
path="roles/:roleName"
|
|
159
|
+
element={
|
|
160
|
+
<ResourceAccessGuard resource="roles" action="show">
|
|
161
|
+
<RoleDetailRoute returnTo="show" />
|
|
162
|
+
</ResourceAccessGuard>
|
|
163
|
+
}
|
|
164
|
+
/>
|
|
165
|
+
</Route>
|
|
137
166
|
</Route>
|
|
138
167
|
{extensionRouteElements}
|
|
139
168
|
<Route path="*" element={<ErrorComponent />} />
|
|
@@ -159,7 +188,8 @@ function App() {
|
|
|
159
188
|
<Toaster />
|
|
160
189
|
<UnsavedChangesNotifier />
|
|
161
190
|
<DocumentTitleHandler appName="NocoBase" />
|
|
162
|
-
|
|
191
|
+
</Refine>
|
|
192
|
+
</SystemSettingsProvider>
|
|
163
193
|
</TooltipProvider>
|
|
164
194
|
</ThemeProvider>
|
|
165
195
|
</BrowserRouter>
|
package/src/app/extension.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ResourceProps } from "@refinedev/core";
|
|
2
2
|
import type { ComponentType, PropsWithChildren, ReactElement } from "react";
|
|
3
3
|
|
|
4
4
|
export type AppExtension = {
|
|
@@ -7,5 +7,4 @@ export type AppExtension = {
|
|
|
7
7
|
routes?: ReactElement;
|
|
8
8
|
Provider?: ComponentType<PropsWithChildren>;
|
|
9
9
|
UserMenuItems?: ComponentType;
|
|
10
|
-
i18nProvider?: I18nProvider;
|
|
11
10
|
};
|
package/src/app/extensions.tsx
CHANGED
|
@@ -18,20 +18,6 @@ export const extensionRouteElements = appExtensions
|
|
|
18
18
|
.map((extension) => extension.routes)
|
|
19
19
|
.filter((routes): routes is NonNullable<typeof routes> => Boolean(routes));
|
|
20
20
|
|
|
21
|
-
const i18nExtensions = appExtensions.filter(
|
|
22
|
-
(extension) => extension.i18nProvider
|
|
23
|
-
);
|
|
24
|
-
|
|
25
|
-
if (i18nExtensions.length > 1) {
|
|
26
|
-
console.warn(
|
|
27
|
-
`Multiple app extensions provide i18nProvider (${i18nExtensions
|
|
28
|
-
.map((extension) => extension.id)
|
|
29
|
-
.join(", ")}). Using ${i18nExtensions[0].id}.`
|
|
30
|
-
);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export const extensionI18nProvider = i18nExtensions[0]?.i18nProvider;
|
|
34
|
-
|
|
35
21
|
export const extensionUserMenuItems = appExtensions
|
|
36
22
|
.filter((extension) => extension.UserMenuItems)
|
|
37
23
|
.map((extension) => ({
|
|
@@ -3,9 +3,12 @@
|
|
|
3
3
|
import { Fragment, useMemo } from "react";
|
|
4
4
|
import { Home } from "lucide-react";
|
|
5
5
|
import {
|
|
6
|
+
type IResourceItem,
|
|
6
7
|
matchResourceFromRoute,
|
|
7
8
|
useBreadcrumb,
|
|
9
|
+
useGetToPath,
|
|
8
10
|
useLink,
|
|
11
|
+
useParsed,
|
|
9
12
|
useResourceParams,
|
|
10
13
|
useTranslate,
|
|
11
14
|
useUserFriendlyName,
|
|
@@ -24,9 +27,42 @@ export function Breadcrumb() {
|
|
|
24
27
|
const Link = useLink();
|
|
25
28
|
const { breadcrumbs } = useBreadcrumb();
|
|
26
29
|
const { resources } = useResourceParams();
|
|
30
|
+
const { pathname } = useParsed();
|
|
31
|
+
const getToPath = useGetToPath();
|
|
27
32
|
const translate = useTranslate();
|
|
28
33
|
const getUserFriendlyName = useUserFriendlyName();
|
|
29
34
|
const rootRouteResource = matchResourceFromRoute("/", resources);
|
|
35
|
+
const resolvedBreadcrumbs = useMemo(() => {
|
|
36
|
+
if (breadcrumbs.length > 0) return breadcrumbs;
|
|
37
|
+
|
|
38
|
+
const closestMatch = findClosestRouteMatch(pathname, resources);
|
|
39
|
+
if (!closestMatch.resource) return breadcrumbs;
|
|
40
|
+
|
|
41
|
+
const items: Array<{ label: string; href?: string }> = [
|
|
42
|
+
{
|
|
43
|
+
label:
|
|
44
|
+
closestMatch.resource.meta?.label ?? closestMatch.resource.name,
|
|
45
|
+
href: getToPath({ resource: closestMatch.resource, action: "list" }),
|
|
46
|
+
},
|
|
47
|
+
];
|
|
48
|
+
const nestedAction = getNestedRouteAction(
|
|
49
|
+
pathname,
|
|
50
|
+
closestMatch.matchedSegmentCount
|
|
51
|
+
) ??
|
|
52
|
+
(closestMatch.action && closestMatch.action !== "list"
|
|
53
|
+
? closestMatch.action
|
|
54
|
+
: undefined);
|
|
55
|
+
if (nestedAction) {
|
|
56
|
+
items.push({
|
|
57
|
+
label: translate(
|
|
58
|
+
`buttons.${nestedAction}`,
|
|
59
|
+
nestedAction.charAt(0).toUpperCase() + nestedAction.slice(1)
|
|
60
|
+
),
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return items;
|
|
65
|
+
}, [breadcrumbs, getToPath, pathname, resources, translate]);
|
|
30
66
|
|
|
31
67
|
const breadCrumbItems = useMemo(() => {
|
|
32
68
|
const list: {
|
|
@@ -47,7 +83,7 @@ export function Breadcrumb() {
|
|
|
47
83
|
),
|
|
48
84
|
});
|
|
49
85
|
|
|
50
|
-
for (const { label, href } of
|
|
86
|
+
for (const { label, href } of resolvedBreadcrumbs) {
|
|
51
87
|
const matchingResource = resources.find((resource) => {
|
|
52
88
|
const metaLabel = resource.meta?.label;
|
|
53
89
|
return (
|
|
@@ -80,9 +116,9 @@ export function Breadcrumb() {
|
|
|
80
116
|
|
|
81
117
|
return list;
|
|
82
118
|
}, [
|
|
83
|
-
breadcrumbs,
|
|
84
119
|
getUserFriendlyName,
|
|
85
120
|
Link,
|
|
121
|
+
resolvedBreadcrumbs,
|
|
86
122
|
resources,
|
|
87
123
|
rootRouteResource,
|
|
88
124
|
translate,
|
|
@@ -114,4 +150,33 @@ export function Breadcrumb() {
|
|
|
114
150
|
);
|
|
115
151
|
}
|
|
116
152
|
|
|
153
|
+
function findClosestRouteMatch(
|
|
154
|
+
pathname: string | undefined,
|
|
155
|
+
resources: IResourceItem[]
|
|
156
|
+
) {
|
|
157
|
+
const segments = pathname?.split("/").filter(Boolean) ?? [];
|
|
158
|
+
|
|
159
|
+
for (let length = segments.length; length >= 0; length -= 1) {
|
|
160
|
+
const route = `/${segments.slice(0, length).join("/")}`;
|
|
161
|
+
const match = matchResourceFromRoute(route, resources);
|
|
162
|
+
if (match.resource) {
|
|
163
|
+
return { ...match, matchedSegmentCount: length };
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return { resource: undefined, matchedSegmentCount: 0 };
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function getNestedRouteAction(
|
|
171
|
+
pathname: string | undefined,
|
|
172
|
+
matchedSegmentCount: number
|
|
173
|
+
) {
|
|
174
|
+
const segments = pathname?.split("/").filter(Boolean) ?? [];
|
|
175
|
+
const action = segments[matchedSegmentCount];
|
|
176
|
+
|
|
177
|
+
return action && ["create", "edit", "show", "clone"].includes(action)
|
|
178
|
+
? action
|
|
179
|
+
: undefined;
|
|
180
|
+
}
|
|
181
|
+
|
|
117
182
|
Breadcrumb.displayName = "Breadcrumb";
|
|
@@ -218,7 +218,7 @@ function SidebarDropdownItem({
|
|
|
218
218
|
selectedKey,
|
|
219
219
|
Link,
|
|
220
220
|
}: MenuItemProps & { Link: ReturnType<typeof useLink> }) {
|
|
221
|
-
const isSelected = item
|
|
221
|
+
const isSelected = isTreeItemSelected(item, selectedKey);
|
|
222
222
|
const displayName = useMenuItemLabel(item);
|
|
223
223
|
|
|
224
224
|
return (
|
|
@@ -239,7 +239,7 @@ function SidebarDropdownItem({
|
|
|
239
239
|
}
|
|
240
240
|
|
|
241
241
|
function SidebarItemLink({ item, selectedKey }: MenuItemProps) {
|
|
242
|
-
const isSelected = item
|
|
242
|
+
const isSelected = isTreeItemSelected(item, selectedKey);
|
|
243
243
|
|
|
244
244
|
return <SidebarButton item={item} isSelected={isSelected} asLink={true} />;
|
|
245
245
|
}
|
|
@@ -11,12 +11,13 @@ import { cn } from "@/lib/utils";
|
|
|
11
11
|
|
|
12
12
|
type ListViewProps = PropsWithChildren<{
|
|
13
13
|
className?: string;
|
|
14
|
+
resource?: string;
|
|
14
15
|
}>;
|
|
15
16
|
|
|
16
|
-
export function ListView({ children, className }: ListViewProps) {
|
|
17
|
+
export function ListView({ children, className, resource }: ListViewProps) {
|
|
17
18
|
return (
|
|
18
19
|
<div className={cn("flex flex-col", "gap-6", className)}>
|
|
19
|
-
<ListViewHeader />
|
|
20
|
+
<ListViewHeader resource={resource} />
|
|
20
21
|
{children}
|
|
21
22
|
</div>
|
|
22
23
|
);
|