@nocobase/client-v2 2.1.0-beta.34 → 2.1.0-beta.36
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/es/BaseApplication.d.ts +7 -1
- package/es/PluginManager.d.ts +2 -0
- package/es/components/PoweredBy.d.ts +18 -0
- package/es/components/SwitchLanguage.d.ts +11 -0
- package/es/components/form/DialogFormLayout.d.ts +75 -0
- package/es/components/form/DrawerFormLayout.d.ts +11 -11
- package/es/components/form/PasswordInput.d.ts +40 -0
- package/es/components/form/RemoteSelect.d.ts +79 -0
- package/es/components/form/index.d.ts +3 -0
- package/es/components/form/table/styles.d.ts +10 -0
- package/es/components/index.d.ts +2 -0
- package/es/flow/models/base/ActionModelCore.d.ts +6 -0
- package/es/flow/models/base/GridModel.d.ts +2 -0
- package/es/flow/models/blocks/filter-form/FilterFormBlockModel.d.ts +9 -1
- package/es/flow/utils/dataScopeFormValueClear.d.ts +14 -0
- package/es/flow-compat/passwordUtils.d.ts +1 -1
- package/es/hooks/index.d.ts +2 -0
- package/es/hooks/useCurrentAppInfo.d.ts +9 -0
- package/es/index.mjs +117 -105
- package/es/json-logic/globalOperators.d.ts +11 -0
- package/es/nocobase-buildin-plugin/index.d.ts +25 -0
- package/es/utils/appVersionHTML.d.ts +10 -0
- package/es/utils/globalDeps.d.ts +7 -0
- package/es/utils/index.d.ts +1 -0
- package/es/utils/remotePlugins.d.ts +4 -1
- package/lib/index.js +120 -108
- package/package.json +7 -6
- package/src/BaseApplication.tsx +11 -3
- package/src/PluginManager.ts +2 -0
- package/src/PluginSettingsManager.ts +2 -1
- package/src/__tests__/PluginSettingsManager.test.ts +19 -0
- package/src/__tests__/PoweredBy.test.tsx +130 -0
- package/src/__tests__/app.test.tsx +39 -0
- package/src/__tests__/nocobase-buildin-plugin-auth.test.tsx +39 -72
- package/src/__tests__/remotePlugins.test.ts +203 -0
- package/src/__tests__/useCurrentRoles.test.tsx +100 -0
- package/src/components/PoweredBy.tsx +71 -0
- package/src/components/README.md +314 -0
- package/src/components/README.zh-CN.md +312 -0
- package/src/components/SwitchLanguage.tsx +48 -0
- package/src/components/form/DialogFormLayout.tsx +111 -0
- package/src/components/form/DrawerFormLayout.tsx +13 -32
- package/src/components/form/PasswordInput.tsx +211 -0
- package/src/components/form/RemoteSelect.tsx +137 -0
- package/src/components/form/index.tsx +3 -0
- package/src/components/form/table/Table.tsx +2 -1
- package/src/components/form/table/styles.ts +19 -0
- package/src/components/index.ts +2 -0
- package/src/css-variable/CSSVariableProvider.tsx +10 -1
- package/src/flow/actions/__tests__/dataScopeFormValueClear.test.ts +96 -0
- package/src/flow/actions/dataScope.tsx +3 -0
- package/src/flow/actions/filterFormDefaultValues.tsx +1 -2
- package/src/flow/admin-shell/admin-layout/AdminLayoutComponent.tsx +1 -4
- package/src/flow/admin-shell/admin-layout/HelpLite.tsx +7 -33
- package/src/flow/components/BlockItemCard.tsx +2 -2
- package/src/flow/models/base/ActionModel.tsx +8 -7
- package/src/flow/models/base/ActionModelCore.tsx +15 -7
- package/src/flow/models/base/GridModel.tsx +93 -36
- package/src/flow/models/base/__tests__/GridModel.visibleLayout.test.ts +83 -11
- package/src/flow/models/blocks/details/DetailsItemModel.tsx +2 -0
- package/src/flow/models/blocks/filter-form/FilterFormBlockModel.tsx +329 -5
- package/src/flow/models/blocks/filter-form/__tests__/defaultValues.wiring.test.ts +337 -0
- package/src/flow/models/blocks/form/FormItemModel.tsx +5 -3
- package/src/flow/models/blocks/form/__tests__/FormItemModel.defineChildren.test.ts +108 -0
- package/src/flow/models/blocks/table/TableActionsColumnModel.tsx +5 -0
- package/src/flow/models/blocks/table/TableColumnModel.tsx +2 -0
- package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/SubTableColumnModel.tsx +2 -0
- package/src/flow/utils/dataScopeFormValueClear.ts +278 -0
- package/src/hooks/index.ts +2 -0
- package/src/hooks/useCurrentAppInfo.ts +36 -0
- package/src/json-logic/globalOperators.js +731 -0
- package/src/nocobase-buildin-plugin/index.tsx +70 -16
- package/src/utils/appVersionHTML.ts +28 -0
- package/src/utils/globalDeps.ts +47 -31
- package/src/utils/index.tsx +2 -0
- package/src/utils/remotePlugins.ts +119 -13
|
@@ -14,8 +14,11 @@ import { configRequirejs, defineDevPlugins, getPlugins } from '../utils/remotePl
|
|
|
14
14
|
describe('client-v2 remotePlugins', () => {
|
|
15
15
|
afterEach(() => {
|
|
16
16
|
window.define = undefined;
|
|
17
|
+
window.__nocobase_app_dev_plugins__ = undefined;
|
|
17
18
|
});
|
|
18
19
|
|
|
20
|
+
const createModuleUrl = (code: string) => `data:text/javascript;charset=utf-8,${encodeURIComponent(code)}`;
|
|
21
|
+
|
|
19
22
|
it('should define dev plugins with /client-v2 module ids', () => {
|
|
20
23
|
class DemoPlugin extends Plugin {}
|
|
21
24
|
|
|
@@ -29,6 +32,61 @@ describe('client-v2 remotePlugins', () => {
|
|
|
29
32
|
expect(mockDefine).toHaveBeenCalledWith('@nocobase/demo/client-v2', expect.any(Function));
|
|
30
33
|
});
|
|
31
34
|
|
|
35
|
+
it('should preserve named exports from dev plugin modules', () => {
|
|
36
|
+
class DemoPlugin extends Plugin {}
|
|
37
|
+
class DemoActionModel {}
|
|
38
|
+
|
|
39
|
+
const mockDefine: any = vi.fn();
|
|
40
|
+
window.define = mockDefine;
|
|
41
|
+
|
|
42
|
+
const pluginModule = {
|
|
43
|
+
default: DemoPlugin,
|
|
44
|
+
DemoActionModel,
|
|
45
|
+
};
|
|
46
|
+
defineDevPlugins({
|
|
47
|
+
'@nocobase/demo': pluginModule,
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
const moduleFactory = mockDefine.mock.calls[0]?.[1];
|
|
51
|
+
expect(mockDefine).toHaveBeenCalledWith('@nocobase/demo/client-v2', expect.any(Function));
|
|
52
|
+
expect(moduleFactory()).toBe(pluginModule);
|
|
53
|
+
expect(window.__nocobase_app_dev_plugins__['@nocobase/demo/client-v2']).toBe(pluginModule);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('should expose named exports from devDynamicImport modules to RequireJS consumers', async () => {
|
|
57
|
+
class DemoPlugin extends Plugin {}
|
|
58
|
+
class DemoActionModel {}
|
|
59
|
+
|
|
60
|
+
const requirejs: any = {
|
|
61
|
+
requirejs: vi.fn(),
|
|
62
|
+
};
|
|
63
|
+
requirejs.requirejs.config = vi.fn();
|
|
64
|
+
|
|
65
|
+
const mockDefine: any = vi.fn();
|
|
66
|
+
window.define = mockDefine;
|
|
67
|
+
|
|
68
|
+
const plugins = await getPlugins({
|
|
69
|
+
requirejs,
|
|
70
|
+
pluginData: [
|
|
71
|
+
{
|
|
72
|
+
name: '@nocobase/demo',
|
|
73
|
+
packageName: '@nocobase/demo',
|
|
74
|
+
url: 'https://demo.com/dist/client-v2/index.js',
|
|
75
|
+
},
|
|
76
|
+
] as any,
|
|
77
|
+
devDynamicImport: vi.fn().mockResolvedValue({ default: DemoPlugin, DemoActionModel }) as any,
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
const moduleFactory = mockDefine.mock.calls.find((call) => call[0] === '@nocobase/demo/client-v2')?.[1];
|
|
81
|
+
|
|
82
|
+
expect(plugins).toEqual([['@nocobase/demo', DemoPlugin]]);
|
|
83
|
+
expect(moduleFactory()).toEqual({ default: DemoPlugin, DemoActionModel });
|
|
84
|
+
expect(window.__nocobase_app_dev_plugins__['@nocobase/demo/client-v2']).toEqual({
|
|
85
|
+
default: DemoPlugin,
|
|
86
|
+
DemoActionModel,
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
|
|
32
90
|
it('should not define /client aliases when loading v2 plugins', async () => {
|
|
33
91
|
class DemoPlugin extends Plugin {}
|
|
34
92
|
|
|
@@ -58,6 +116,151 @@ describe('client-v2 remotePlugins', () => {
|
|
|
58
116
|
expect(mockDefine).not.toHaveBeenCalledWith('@nocobase/demo/client', expect.any(Function));
|
|
59
117
|
});
|
|
60
118
|
|
|
119
|
+
it('should request remote plugins when devDynamicImport only resolves some plugins', async () => {
|
|
120
|
+
class DemoPlugin extends Plugin {}
|
|
121
|
+
|
|
122
|
+
const remoteFn = vi.fn();
|
|
123
|
+
const requirejs: any = {
|
|
124
|
+
requirejs: (pluginData, resolve) => {
|
|
125
|
+
remoteFn();
|
|
126
|
+
resolve({ default: DemoPlugin });
|
|
127
|
+
},
|
|
128
|
+
};
|
|
129
|
+
requirejs.requirejs.config = vi.fn();
|
|
130
|
+
|
|
131
|
+
const mockDefine: any = vi.fn();
|
|
132
|
+
window.define = mockDefine;
|
|
133
|
+
|
|
134
|
+
const plugins = await getPlugins({
|
|
135
|
+
requirejs,
|
|
136
|
+
pluginData: [
|
|
137
|
+
{
|
|
138
|
+
name: '@nocobase/demo',
|
|
139
|
+
packageName: '@nocobase/demo',
|
|
140
|
+
url: 'https://demo.com/dist/client-v2/index.js',
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
name: '@nocobase/remote',
|
|
144
|
+
packageName: '@nocobase/remote',
|
|
145
|
+
url: 'https://remote.com/dist/client-v2/index.js',
|
|
146
|
+
},
|
|
147
|
+
] as any,
|
|
148
|
+
devDynamicImport: ((packageName) => {
|
|
149
|
+
if (packageName === '@nocobase/demo') {
|
|
150
|
+
return Promise.resolve({ default: DemoPlugin });
|
|
151
|
+
}
|
|
152
|
+
return Promise.resolve(null);
|
|
153
|
+
}) as any,
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
expect(plugins).toEqual([
|
|
157
|
+
['@nocobase/demo', DemoPlugin],
|
|
158
|
+
['@nocobase/remote', DemoPlugin],
|
|
159
|
+
]);
|
|
160
|
+
expect(remoteFn).toHaveBeenCalledTimes(1);
|
|
161
|
+
expect(mockDefine).toHaveBeenCalledTimes(1);
|
|
162
|
+
expect(requirejs.requirejs.config).toHaveBeenCalledWith({
|
|
163
|
+
waitSeconds: 120,
|
|
164
|
+
paths: {
|
|
165
|
+
'@nocobase/remote/client-v2': 'https://remote.com/dist/client-v2/index.js',
|
|
166
|
+
},
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it('should register ESM dev plugins before importing dependent plugins', async () => {
|
|
171
|
+
const requirejs: any = {
|
|
172
|
+
requirejs: vi.fn(),
|
|
173
|
+
};
|
|
174
|
+
requirejs.requirejs.config = vi.fn();
|
|
175
|
+
|
|
176
|
+
const mockDefine: any = vi.fn();
|
|
177
|
+
window.define = mockDefine;
|
|
178
|
+
|
|
179
|
+
const plugins = await getPlugins({
|
|
180
|
+
requirejs,
|
|
181
|
+
pluginData: [
|
|
182
|
+
{
|
|
183
|
+
name: '@nocobase/dependent',
|
|
184
|
+
packageName: '@nocobase/dependent',
|
|
185
|
+
url: createModuleUrl(`
|
|
186
|
+
const dependency = window.__nocobase_app_dev_plugins__ && window.__nocobase_app_dev_plugins__['@nocobase/dependency/client-v2'];
|
|
187
|
+
export default class DependentPlugin extends dependency.default {
|
|
188
|
+
static SharedBase = dependency.SharedBase;
|
|
189
|
+
}
|
|
190
|
+
`),
|
|
191
|
+
devMode: 'esm',
|
|
192
|
+
appDevDependencies: ['@nocobase/dependency'],
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
name: '@nocobase/dependency',
|
|
196
|
+
packageName: '@nocobase/dependency',
|
|
197
|
+
url: createModuleUrl('export class SharedBase {}; export default class DependencyPlugin {}'),
|
|
198
|
+
devMode: 'esm',
|
|
199
|
+
},
|
|
200
|
+
] as any,
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
const dependencyModule = window.__nocobase_app_dev_plugins__['@nocobase/dependency/client-v2'] as {
|
|
204
|
+
default?: unknown;
|
|
205
|
+
SharedBase?: unknown;
|
|
206
|
+
};
|
|
207
|
+
const dependentPlugin = plugins[1][1] as (typeof plugins)[number][1] & { SharedBase?: unknown };
|
|
208
|
+
|
|
209
|
+
expect(plugins.map(([name]) => name)).toEqual(['@nocobase/dependency', '@nocobase/dependent']);
|
|
210
|
+
expect(dependencyModule.SharedBase).toBeDefined();
|
|
211
|
+
expect(plugins[0][1]).toBe(dependencyModule.default);
|
|
212
|
+
expect(dependentPlugin.SharedBase).toBe(dependencyModule.SharedBase);
|
|
213
|
+
expect(mockDefine).toHaveBeenCalledWith('@nocobase/dependency/client-v2', expect.any(Function));
|
|
214
|
+
expect(requirejs.requirejs.config).not.toHaveBeenCalled();
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
it('should load RequireJS dependencies before ESM dev plugins', async () => {
|
|
218
|
+
class DependencyPlugin extends Plugin {}
|
|
219
|
+
const SharedBase = { source: 'remote' };
|
|
220
|
+
const dependencyModule = {
|
|
221
|
+
SharedBase,
|
|
222
|
+
default: DependencyPlugin,
|
|
223
|
+
};
|
|
224
|
+
const requirejs: any = {
|
|
225
|
+
requirejs: vi.fn((packageNames, resolve) => {
|
|
226
|
+
expect(packageNames).toEqual(['@nocobase/remote-dependency/client-v2']);
|
|
227
|
+
resolve(dependencyModule);
|
|
228
|
+
}),
|
|
229
|
+
};
|
|
230
|
+
requirejs.requirejs.config = vi.fn();
|
|
231
|
+
|
|
232
|
+
window.define = vi.fn();
|
|
233
|
+
|
|
234
|
+
const plugins = await getPlugins({
|
|
235
|
+
requirejs,
|
|
236
|
+
pluginData: [
|
|
237
|
+
{
|
|
238
|
+
name: '@nocobase/dependent',
|
|
239
|
+
packageName: '@nocobase/dependent',
|
|
240
|
+
url: createModuleUrl(`
|
|
241
|
+
const dependency = window.__nocobase_app_dev_plugins__ && window.__nocobase_app_dev_plugins__['@nocobase/remote-dependency/client-v2'];
|
|
242
|
+
export default class DependentPlugin extends dependency.default {
|
|
243
|
+
static SharedBase = dependency.SharedBase;
|
|
244
|
+
}
|
|
245
|
+
`),
|
|
246
|
+
devMode: 'esm',
|
|
247
|
+
appDevDependencies: ['@nocobase/remote-dependency'],
|
|
248
|
+
},
|
|
249
|
+
{
|
|
250
|
+
name: '@nocobase/remote-dependency',
|
|
251
|
+
packageName: '@nocobase/remote-dependency',
|
|
252
|
+
url: 'https://dependency.com',
|
|
253
|
+
},
|
|
254
|
+
] as any,
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
const dependentPlugin = plugins[1][1] as (typeof plugins)[number][1] & { SharedBase?: unknown };
|
|
258
|
+
|
|
259
|
+
expect(plugins.map(([name]) => name)).toEqual(['@nocobase/remote-dependency', '@nocobase/dependent']);
|
|
260
|
+
expect(window.__nocobase_app_dev_plugins__['@nocobase/remote-dependency/client-v2']).toBe(dependencyModule);
|
|
261
|
+
expect(dependentPlugin.SharedBase).toBe(SharedBase);
|
|
262
|
+
});
|
|
263
|
+
|
|
61
264
|
it('should configure remote plugin paths with /client-v2 module ids', () => {
|
|
62
265
|
const requirejs: any = {
|
|
63
266
|
requirejs: {
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file is part of the NocoBase (R) project.
|
|
3
|
+
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
+
* Authors: NocoBase Team.
|
|
5
|
+
*
|
|
6
|
+
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
+
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { FlowEngine, FlowEngineProvider } from '@nocobase/flow-engine';
|
|
11
|
+
import { renderHook } from '@testing-library/react';
|
|
12
|
+
import React from 'react';
|
|
13
|
+
import { describe, expect, it } from 'vitest';
|
|
14
|
+
import { ACLContext } from '../acl';
|
|
15
|
+
import { useCurrentRoles } from '../nocobase-buildin-plugin';
|
|
16
|
+
|
|
17
|
+
type AclContextValue = React.ContextType<typeof ACLContext>;
|
|
18
|
+
|
|
19
|
+
function makeAclValue(allowAnonymous: boolean): AclContextValue {
|
|
20
|
+
return {
|
|
21
|
+
loading: false,
|
|
22
|
+
data: {
|
|
23
|
+
data: { allowAnonymous },
|
|
24
|
+
meta: {},
|
|
25
|
+
},
|
|
26
|
+
refresh: async () => undefined,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function makeEngineWithUser(user: { roles?: Array<{ name: string; title?: string }> } | null): FlowEngine {
|
|
31
|
+
const engine = new FlowEngine();
|
|
32
|
+
if (user != null) {
|
|
33
|
+
engine.context.defineProperty('user', { value: user });
|
|
34
|
+
}
|
|
35
|
+
return engine;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function makeWrapper(opts: { engine: FlowEngine; acl: AclContextValue }) {
|
|
39
|
+
const Wrapper: React.FC = ({ children }) => (
|
|
40
|
+
<FlowEngineProvider engine={opts.engine}>
|
|
41
|
+
<ACLContext.Provider value={opts.acl}>{children}</ACLContext.Provider>
|
|
42
|
+
</FlowEngineProvider>
|
|
43
|
+
);
|
|
44
|
+
return Wrapper;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
describe('useCurrentRoles', () => {
|
|
48
|
+
it('returns roles from flowEngine.context.user, dropping the synthetic __union__ entry', () => {
|
|
49
|
+
// `__union__` is a server-side marker for the merged-roles pseudo role and
|
|
50
|
+
// must never appear in user-facing role pickers — guards against a regression
|
|
51
|
+
// that broke role assignment in API Keys / SwitchRole pages.
|
|
52
|
+
const wrapper = makeWrapper({
|
|
53
|
+
engine: makeEngineWithUser({
|
|
54
|
+
roles: [
|
|
55
|
+
{ name: '__union__', title: 'Union' },
|
|
56
|
+
{ name: 'root', title: 'Root' },
|
|
57
|
+
{ name: 'member', title: 'Member' },
|
|
58
|
+
],
|
|
59
|
+
}),
|
|
60
|
+
acl: makeAclValue(false),
|
|
61
|
+
});
|
|
62
|
+
const { result } = renderHook(() => useCurrentRoles(), { wrapper });
|
|
63
|
+
expect(result.current).toEqual([
|
|
64
|
+
{ name: 'root', title: 'Root' },
|
|
65
|
+
{ name: 'member', title: 'Member' },
|
|
66
|
+
]);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('appends an anonymous role when ACL allowAnonymous is true', () => {
|
|
70
|
+
const wrapper = makeWrapper({
|
|
71
|
+
engine: makeEngineWithUser({ roles: [{ name: 'root', title: 'Root' }] }),
|
|
72
|
+
acl: makeAclValue(true),
|
|
73
|
+
});
|
|
74
|
+
const { result } = renderHook(() => useCurrentRoles(), { wrapper });
|
|
75
|
+
expect(result.current).toEqual([
|
|
76
|
+
{ name: 'root', title: 'Root' },
|
|
77
|
+
{ name: 'anonymous', title: 'Anonymous' },
|
|
78
|
+
]);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('compiles {{t(...)}} templates in role.title via flowEngine.context.t', () => {
|
|
82
|
+
const engine = makeEngineWithUser({ roles: [{ name: 'admin', title: '{{t("Admin")}}' }] });
|
|
83
|
+
engine.context.defineProperty('t', { value: () => 'Compiled Title' });
|
|
84
|
+
const wrapper = makeWrapper({ engine, acl: makeAclValue(false) });
|
|
85
|
+
const { result } = renderHook(() => useCurrentRoles(), { wrapper });
|
|
86
|
+
expect(result.current).toEqual([{ name: 'admin', title: 'Compiled Title' }]);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('returns an empty array when no user has been written to engine.context', () => {
|
|
90
|
+
const wrapper = makeWrapper({ engine: makeEngineWithUser(null), acl: makeAclValue(false) });
|
|
91
|
+
const { result } = renderHook(() => useCurrentRoles(), { wrapper });
|
|
92
|
+
expect(result.current).toEqual([]);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('returns just anonymous when no user is set but allowAnonymous is true', () => {
|
|
96
|
+
const wrapper = makeWrapper({ engine: makeEngineWithUser(null), acl: makeAclValue(true) });
|
|
97
|
+
const { result } = renderHook(() => useCurrentRoles(), { wrapper });
|
|
98
|
+
expect(result.current).toEqual([{ name: 'anonymous', title: 'Anonymous' }]);
|
|
99
|
+
});
|
|
100
|
+
});
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file is part of the NocoBase (R) project.
|
|
3
|
+
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
+
* Authors: NocoBase Team.
|
|
5
|
+
*
|
|
6
|
+
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
+
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { css, cx } from '@emotion/css';
|
|
11
|
+
import { parseHTML } from '@nocobase/utils/client';
|
|
12
|
+
import { theme } from 'antd';
|
|
13
|
+
import React from 'react';
|
|
14
|
+
import { useTranslation } from 'react-i18next';
|
|
15
|
+
import { useCurrentAppInfo } from '../hooks/useCurrentAppInfo';
|
|
16
|
+
import { usePlugin } from '../hooks/usePlugin';
|
|
17
|
+
import { getAppVersionHTML } from '../utils/appVersionHTML';
|
|
18
|
+
|
|
19
|
+
const homePageUrls: Record<string, string> = {
|
|
20
|
+
'en-US': 'https://www.nocobase.com',
|
|
21
|
+
'zh-CN': 'https://www.nocobase.com/cn/',
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Footer brand rendered on auth pages and other layout entry points. Falls
|
|
26
|
+
* back to "Powered by NocoBase" when `@nocobase/plugin-custom-brand` is not
|
|
27
|
+
* installed; otherwise renders the plugin's HTML template with the
|
|
28
|
+
* `{{appVersion}}` placeholder substituted. The version is escaped via
|
|
29
|
+
* `getAppVersionHTML` so a malicious app version cannot inject script tags.
|
|
30
|
+
*/
|
|
31
|
+
export function PoweredBy() {
|
|
32
|
+
const { i18n } = useTranslation();
|
|
33
|
+
const { token } = theme.useToken();
|
|
34
|
+
const customBrandPlugin: any = usePlugin('@nocobase/plugin-custom-brand');
|
|
35
|
+
const appInfo = useCurrentAppInfo();
|
|
36
|
+
const appVersion = getAppVersionHTML(appInfo?.version);
|
|
37
|
+
const homePage = homePageUrls[i18n.language] || homePageUrls['en-US'];
|
|
38
|
+
const brandStyle = css`
|
|
39
|
+
text-align: center;
|
|
40
|
+
color: ${token.colorTextDescription};
|
|
41
|
+
a {
|
|
42
|
+
color: ${token.colorTextDescription};
|
|
43
|
+
&:hover {
|
|
44
|
+
color: ${token.colorText};
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
`;
|
|
48
|
+
const customBrand = customBrandPlugin?.options?.options?.brand;
|
|
49
|
+
|
|
50
|
+
if (customBrand) {
|
|
51
|
+
return (
|
|
52
|
+
<div
|
|
53
|
+
className={cx(brandStyle, 'nb-brand')}
|
|
54
|
+
dangerouslySetInnerHTML={{
|
|
55
|
+
__html: parseHTML(customBrand, { appVersion }),
|
|
56
|
+
}}
|
|
57
|
+
/>
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return (
|
|
62
|
+
<div className={brandStyle}>
|
|
63
|
+
Powered by{' '}
|
|
64
|
+
<a href={homePage} target="_blank" rel="noreferrer">
|
|
65
|
+
NocoBase
|
|
66
|
+
</a>
|
|
67
|
+
</div>
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export default PoweredBy;
|