@nocobase/client-v2 2.2.0-beta.5 → 2.2.0-beta.7
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/PluginSettingsManager.d.ts +33 -0
- package/es/RouteRepository.d.ts +21 -6
- package/es/components/form/JsonTextArea.d.ts +2 -1
- package/es/components/form/VariableJsonTextArea.d.ts +19 -0
- package/es/components/form/index.d.ts +1 -0
- package/es/components/index.d.ts +1 -0
- package/es/flow/admin-shell/admin-layout/AdminLayoutEntryGuard.d.ts +2 -0
- package/es/flow/admin-shell/admin-layout/AdminLayoutMenuFlowUtils.d.ts +2 -1
- package/es/flow/admin-shell/admin-layout/AdminLayoutMenuModels.d.ts +2 -0
- package/es/flow/admin-shell/admin-layout/AdminLayoutMenuUtils.d.ts +3 -2
- package/es/flow/admin-shell/admin-layout/AdminLayoutSlotModels.d.ts +4 -0
- package/es/flow/admin-shell/admin-layout/resolveAdminRouteRuntimeTarget.d.ts +6 -0
- package/es/flow/components/FieldAssignRulesEditor.d.ts +3 -1
- package/es/flow/models/base/GridModel.d.ts +1 -1
- package/es/flow/models/blocks/filter-form/FilterFormBlockModel.d.ts +5 -0
- package/es/flow/models/blocks/form/FormBlockModel.d.ts +4 -0
- package/es/flow/models/blocks/form/value-runtime/rules.d.ts +21 -0
- package/es/flow/models/blocks/form/value-runtime/runtime.d.ts +21 -0
- package/es/flow/models/blocks/form/value-runtime/types.d.ts +2 -2
- package/es/flow-compat/FieldValidation.d.ts +2 -1
- package/es/index.d.ts +2 -1
- package/es/index.mjs +145 -119
- package/lib/index.js +148 -122
- package/package.json +7 -7
- package/src/PluginSettingsManager.ts +53 -0
- package/src/RouteRepository.ts +126 -24
- package/src/__tests__/PluginSettingsManager.test.ts +13 -0
- package/src/__tests__/RouteRepository.test.ts +216 -0
- package/src/__tests__/exports.test.ts +16 -0
- package/src/__tests__/plugin-manager.test.tsx +44 -2
- package/src/__tests__/settings-center.test.tsx +40 -1
- package/src/components/form/JsonTextArea.tsx +21 -11
- package/src/components/form/VariableJsonTextArea.tsx +175 -0
- package/src/components/form/__tests__/JsonTextArea.test.tsx +43 -0
- package/src/components/form/__tests__/VariableJsonTextArea.test.tsx +86 -0
- package/src/components/form/index.tsx +1 -0
- package/src/components/index.ts +1 -0
- package/src/flow/__tests__/FlowRoute.test.tsx +443 -5
- package/src/flow/actions/__tests__/dataScopeFilter.test.ts +58 -0
- package/src/flow/actions/__tests__/fieldLinkageRules.scopeDepth.test.ts +150 -1
- package/src/flow/actions/__tests__/linkageAssignField.legacy.test.ts +135 -0
- package/src/flow/actions/__tests__/linkageRules.hiddenSync.restore.test.ts +19 -5
- package/src/flow/actions/__tests__/linkageRules.subFormSetFieldProps.test.ts +92 -0
- package/src/flow/actions/customVariable.tsx +1 -2
- package/src/flow/actions/dataScopeFilter.ts +10 -0
- package/src/flow/actions/linkageRules.tsx +122 -34
- package/src/flow/actions/runjs.tsx +2 -14
- package/src/flow/actions/validation.tsx +62 -30
- package/src/flow/admin-shell/admin-layout/AdminLayoutComponent.tsx +33 -10
- package/src/flow/admin-shell/admin-layout/AdminLayoutEntryGuard.test.tsx +310 -0
- package/src/flow/admin-shell/admin-layout/AdminLayoutEntryGuard.tsx +44 -8
- package/src/flow/admin-shell/admin-layout/AdminLayoutMenuFlowUtils.ts +5 -3
- package/src/flow/admin-shell/admin-layout/AdminLayoutMenuModels.tsx +61 -9
- package/src/flow/admin-shell/admin-layout/AdminLayoutMenuUtils.tsx +71 -8
- package/src/flow/admin-shell/admin-layout/AdminLayoutModel.tsx +1 -1
- package/src/flow/admin-shell/admin-layout/AdminLayoutSlotModels.tsx +21 -8
- package/src/flow/admin-shell/admin-layout/__tests__/AdminLayoutComponent.test.tsx +188 -0
- package/src/flow/admin-shell/admin-layout/__tests__/AdminLayoutMenuModels.test.ts +99 -0
- package/src/flow/admin-shell/admin-layout/__tests__/TopbarActionsBar.test.tsx +166 -2
- package/src/flow/admin-shell/admin-layout/resolveAdminRouteRuntimeTarget.test.ts +20 -0
- package/src/flow/admin-shell/admin-layout/resolveAdminRouteRuntimeTarget.ts +33 -5
- package/src/flow/components/DefaultValue.tsx +1 -2
- package/src/flow/components/FieldAssignRulesEditor.tsx +128 -21
- package/src/flow/components/FlowRoute.tsx +56 -11
- package/src/flow/components/__tests__/FieldAssignRulesEditor.test.tsx +508 -4
- package/src/flow/components/__tests__/fieldAssignOptions.test.ts +151 -3
- package/src/flow/components/code-editor/__tests__/useCodeRunner.test.tsx +2 -17
- package/src/flow/components/code-editor/hooks/useCodeRunner.ts +2 -14
- package/src/flow/components/code-editor/runjsDiagnostics.ts +8 -45
- package/src/flow/components/fieldAssignOptions.ts +155 -27
- package/src/flow/models/actions/JSActionModel.tsx +2 -7
- package/src/flow/models/actions/JSCollectionActionModel.tsx +2 -7
- package/src/flow/models/actions/JSItemActionModel.tsx +2 -7
- package/src/flow/models/actions/JSRecordActionModel.tsx +2 -7
- package/src/flow/models/base/ActionModel.tsx +12 -1
- package/src/flow/models/base/GridModel.tsx +38 -7
- package/src/flow/models/base/PageModel/PageModel.tsx +4 -1
- package/src/flow/models/base/PageModel/__tests__/PageModel.test.ts +28 -4
- package/src/flow/models/base/__tests__/ActionModel.test.ts +83 -0
- package/src/flow/models/base/__tests__/GridModel.dragSnapshotContainer.test.ts +239 -1
- package/src/flow/models/base/__tests__/transformRowsToSingleColumn.test.ts +48 -0
- package/src/flow/models/blocks/filter-form/FilterFormBlockModel.tsx +31 -3
- package/src/flow/models/blocks/filter-form/FilterFormJSActionModel.tsx +2 -7
- package/src/flow/models/blocks/filter-form/__tests__/defaultValues.wiring.test.ts +77 -0
- package/src/flow/models/blocks/form/EditFormModel.tsx +1 -0
- package/src/flow/models/blocks/form/FormBlockModel.tsx +7 -0
- package/src/flow/models/blocks/form/JSFormActionModel.tsx +2 -7
- package/src/flow/models/blocks/form/__tests__/FormBlockModel.test.tsx +17 -0
- package/src/flow/models/blocks/form/value-runtime/__tests__/runtime.test.ts +880 -102
- package/src/flow/models/blocks/form/value-runtime/rules.ts +445 -13
- package/src/flow/models/blocks/form/value-runtime/runtime.ts +257 -13
- package/src/flow/models/blocks/form/value-runtime/types.ts +2 -2
- package/src/flow/models/blocks/js-block/JSBlock.tsx +2 -7
- package/src/flow/models/blocks/table/JSColumnModel.tsx +1 -9
- package/src/flow/models/blocks/table/TableActionsColumnModel.tsx +1 -1
- package/src/flow/models/blocks/table/TableBlockModel.tsx +13 -3
- package/src/flow/models/blocks/table/__tests__/TableActionsColumnModel.test.tsx +54 -0
- package/src/flow/models/blocks/table/__tests__/TableBlockModel.mobileSettingsButtons.test.tsx +78 -0
- package/src/flow/models/fields/AssociationFieldModel/AssociationFieldModel.tsx +1 -1
- package/src/flow/models/fields/AssociationFieldModel/PopupSubTableFieldModel/PopupSubTableFieldModel.tsx +29 -6
- package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/SubTableField.tsx +13 -1
- package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/__tests__/SubTableFieldModel.reset.test.ts +180 -0
- package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/index.tsx +6 -4
- package/src/flow/models/fields/AssociationFieldModel/__tests__/AssociationFieldModel.updateAssociation.test.ts +72 -0
- package/src/flow/models/fields/InputFieldModel.tsx +14 -22
- package/src/flow/models/fields/JSEditableFieldModel.tsx +2 -11
- package/src/flow/models/fields/JSFieldModel.tsx +2 -7
- package/src/flow/models/fields/JSItemModel.tsx +2 -14
- package/src/flow/models/fields/__tests__/InputFieldModel.test.tsx +17 -0
- package/src/flow/models/topbar/TopbarActionModel.tsx +93 -10
- package/src/flow-compat/FieldValidation.tsx +122 -60
- package/src/flow-compat/Popover.tsx +43 -4
- package/src/flow-compat/__tests__/Popover.test.tsx +34 -0
- package/src/index.ts +8 -1
- package/src/layout-manager/__tests__/LayoutRoute.test.tsx +41 -1
- package/src/nocobase-buildin-plugin/index.tsx +0 -2
- package/src/settings-center/AdminSettingsLayout.tsx +3 -0
- package/src/settings-center/SystemSettingsPage.tsx +0 -1
- package/src/settings-center/plugin-manager/PluginCard.tsx +2 -2
- package/src/settings-center/plugin-manager/index.tsx +3 -0
- package/src/settings-center/utils.tsx +0 -6
|
@@ -14,6 +14,15 @@ import {
|
|
|
14
14
|
collectFieldAssignCascaderOptions,
|
|
15
15
|
} from '../fieldAssignOptions';
|
|
16
16
|
|
|
17
|
+
type TestCollectionField = {
|
|
18
|
+
name?: string;
|
|
19
|
+
title?: string;
|
|
20
|
+
interface?: string;
|
|
21
|
+
target?: string;
|
|
22
|
+
targetCollection?: unknown;
|
|
23
|
+
isAssociationField?: () => boolean;
|
|
24
|
+
};
|
|
25
|
+
|
|
17
26
|
describe('fieldAssignOptions', () => {
|
|
18
27
|
it('marks subform node as non-leaf so Cascader can lazy-load target collection fields', () => {
|
|
19
28
|
const usersCollection = {
|
|
@@ -98,8 +107,58 @@ describe('fieldAssignOptions', () => {
|
|
|
98
107
|
).toBe(true);
|
|
99
108
|
});
|
|
100
109
|
|
|
110
|
+
it('preserves configured display association field paths as nested cascader options', () => {
|
|
111
|
+
const orgNameField = {
|
|
112
|
+
name: 'orgname',
|
|
113
|
+
title: '公司名称(单行文本)',
|
|
114
|
+
interface: 'input',
|
|
115
|
+
type: 'string',
|
|
116
|
+
isAssociationField: () => false,
|
|
117
|
+
};
|
|
118
|
+
const orgCollection = {
|
|
119
|
+
getFields: () => [orgNameField],
|
|
120
|
+
getField: (name: string) => (name === 'orgname' ? orgNameField : null),
|
|
121
|
+
};
|
|
122
|
+
const orgAssociationField = {
|
|
123
|
+
name: 'org_m2o',
|
|
124
|
+
title: 'org_m2o',
|
|
125
|
+
interface: 'm2o',
|
|
126
|
+
type: 'belongsTo',
|
|
127
|
+
target: 'org',
|
|
128
|
+
isAssociationField: () => true,
|
|
129
|
+
targetCollection: orgCollection,
|
|
130
|
+
};
|
|
131
|
+
const rootCollection = {
|
|
132
|
+
getField: (name: string) => (name === 'org_m2o' ? orgAssociationField : null),
|
|
133
|
+
getFields: () => [orgAssociationField],
|
|
134
|
+
};
|
|
135
|
+
const displayAssociationItemModel = {
|
|
136
|
+
props: { label: '公司名称(单行文本)' },
|
|
137
|
+
getStepParams: (flowKey: string, stepKey: string) => {
|
|
138
|
+
if (flowKey === 'fieldSettings' && stepKey === 'init') return { fieldPath: 'org_m2o.orgname' };
|
|
139
|
+
return {};
|
|
140
|
+
},
|
|
141
|
+
subModels: {
|
|
142
|
+
field: {
|
|
143
|
+
context: { collectionField: orgNameField },
|
|
144
|
+
},
|
|
145
|
+
},
|
|
146
|
+
};
|
|
147
|
+
const formBlockModel = {
|
|
148
|
+
context: { collection: rootCollection },
|
|
149
|
+
subModels: { grid: { subModels: { items: [displayAssociationItemModel] } } },
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
const options = collectFieldAssignCascaderOptions({ formBlockModel, t: (s) => s });
|
|
153
|
+
const orgOption = options.find((option) => option.value === 'org_m2o');
|
|
154
|
+
|
|
155
|
+
expect(orgOption?.isLeaf).toBe(false);
|
|
156
|
+
expect(orgOption?.children?.some((child) => child.value === 'orgname' && child.isLeaf === true)).toBe(true);
|
|
157
|
+
expect(options.some((option) => option.value === 'orgname')).toBe(false);
|
|
158
|
+
});
|
|
159
|
+
|
|
101
160
|
it('hides configured association fields deeper than relation / relation / field', () => {
|
|
102
|
-
const makeItem = (fieldPath: string, field:
|
|
161
|
+
const makeItem = (fieldPath: string, field: TestCollectionField, label: string) => ({
|
|
103
162
|
props: { label },
|
|
104
163
|
getStepParams: (flowKey: string, stepKey: string) => {
|
|
105
164
|
if (flowKey === 'fieldSettings' && stepKey === 'init') return { fieldPath };
|
|
@@ -141,9 +200,98 @@ describe('fieldAssignOptions', () => {
|
|
|
141
200
|
};
|
|
142
201
|
|
|
143
202
|
const options = collectFieldAssignCascaderOptions({ formBlockModel, t: (s) => s });
|
|
203
|
+
const usersOption = options.find((item) => item.value === 'users');
|
|
204
|
+
const departmentOption = usersOption?.children?.find((item) => item.value === 'department');
|
|
205
|
+
|
|
206
|
+
expect(departmentOption?.children?.map((item) => item.value)).toContain('name');
|
|
207
|
+
expect(departmentOption?.children?.map((item) => item.value)).not.toContain('company');
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it('hides configured non-association fields under a third association level', () => {
|
|
211
|
+
const codeField = {
|
|
212
|
+
name: 'code',
|
|
213
|
+
title: 'Code',
|
|
214
|
+
interface: 'input',
|
|
215
|
+
isAssociationField: () => false,
|
|
216
|
+
};
|
|
217
|
+
const companyCollection = {
|
|
218
|
+
getField: (name: string) => (name === 'code' ? codeField : null),
|
|
219
|
+
getFields: () => [codeField],
|
|
220
|
+
};
|
|
221
|
+
const companyField = {
|
|
222
|
+
name: 'company',
|
|
223
|
+
title: 'Company',
|
|
224
|
+
interface: 'm2o',
|
|
225
|
+
target: 'companies',
|
|
226
|
+
isAssociationField: () => true,
|
|
227
|
+
targetCollection: companyCollection,
|
|
228
|
+
};
|
|
229
|
+
const nameField = {
|
|
230
|
+
name: 'name',
|
|
231
|
+
title: 'Name',
|
|
232
|
+
interface: 'input',
|
|
233
|
+
isAssociationField: () => false,
|
|
234
|
+
};
|
|
235
|
+
const departmentCollection = {
|
|
236
|
+
getField: (name: string) => (name === 'name' ? nameField : name === 'company' ? companyField : null),
|
|
237
|
+
getFields: () => [nameField, companyField],
|
|
238
|
+
};
|
|
239
|
+
const departmentField = {
|
|
240
|
+
name: 'department',
|
|
241
|
+
title: 'Department',
|
|
242
|
+
interface: 'm2o',
|
|
243
|
+
target: 'departments',
|
|
244
|
+
isAssociationField: () => true,
|
|
245
|
+
targetCollection: departmentCollection,
|
|
246
|
+
};
|
|
247
|
+
const userCollection = {
|
|
248
|
+
getField: (name: string) => (name === 'department' ? departmentField : null),
|
|
249
|
+
getFields: () => [departmentField],
|
|
250
|
+
};
|
|
251
|
+
const usersField = {
|
|
252
|
+
name: 'users',
|
|
253
|
+
title: 'Users',
|
|
254
|
+
interface: 'm2m',
|
|
255
|
+
target: 'users',
|
|
256
|
+
isAssociationField: () => true,
|
|
257
|
+
targetCollection: userCollection,
|
|
258
|
+
};
|
|
259
|
+
const rootCollection = {
|
|
260
|
+
getField: (name: string) => (name === 'users' ? usersField : null),
|
|
261
|
+
getFields: () => [usersField],
|
|
262
|
+
};
|
|
263
|
+
const makeItem = (fieldPath: string, field: TestCollectionField, label: string) => ({
|
|
264
|
+
props: { label },
|
|
265
|
+
getStepParams: (flowKey: string, stepKey: string) => {
|
|
266
|
+
if (flowKey === 'fieldSettings' && stepKey === 'init') return { fieldPath };
|
|
267
|
+
return {};
|
|
268
|
+
},
|
|
269
|
+
subModels: {
|
|
270
|
+
field: {
|
|
271
|
+
context: { collectionField: field },
|
|
272
|
+
},
|
|
273
|
+
},
|
|
274
|
+
});
|
|
275
|
+
const formBlockModel = {
|
|
276
|
+
context: { collection: rootCollection },
|
|
277
|
+
subModels: {
|
|
278
|
+
grid: {
|
|
279
|
+
subModels: {
|
|
280
|
+
items: [
|
|
281
|
+
makeItem('users.department.name', nameField, 'Department name'),
|
|
282
|
+
makeItem('users.department.company.code', codeField, 'Company code'),
|
|
283
|
+
],
|
|
284
|
+
},
|
|
285
|
+
},
|
|
286
|
+
},
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
const options = collectFieldAssignCascaderOptions({ formBlockModel, t: (s) => s });
|
|
290
|
+
const usersOption = options.find((item) => item.value === 'users');
|
|
291
|
+
const departmentOption = usersOption?.children?.find((item) => item.value === 'department');
|
|
144
292
|
|
|
145
|
-
expect(
|
|
146
|
-
expect(
|
|
293
|
+
expect(departmentOption?.children?.map((item) => item.value)).toContain('name');
|
|
294
|
+
expect(departmentOption?.children?.map((item) => item.value)).not.toContain('company');
|
|
147
295
|
});
|
|
148
296
|
|
|
149
297
|
it('hides lazy-loaded association selector options after two association levels', () => {
|
|
@@ -20,8 +20,6 @@ import {
|
|
|
20
20
|
FlowEngineProvider,
|
|
21
21
|
FlowModelRenderer,
|
|
22
22
|
ElementProxy,
|
|
23
|
-
createSafeWindow,
|
|
24
|
-
createSafeDocument,
|
|
25
23
|
createViewScopedEngine,
|
|
26
24
|
} from '@nocobase/flow-engine';
|
|
27
25
|
import { JSEditableFieldModel } from '../../../models/fields/JSEditableFieldModel';
|
|
@@ -167,11 +165,7 @@ describe('useCodeRunner (beforeRender)', () => {
|
|
|
167
165
|
const code = ctx?.inputArgs?.preview?.code || '';
|
|
168
166
|
ctx.onRefReady(ctx.ref, async (el) => {
|
|
169
167
|
ctx.defineProperty('element', { get: () => new ElementProxy(el as any) });
|
|
170
|
-
await ctx.runjs(
|
|
171
|
-
code,
|
|
172
|
-
{ window: createSafeWindow(), document: createSafeDocument() },
|
|
173
|
-
{ preprocessTemplates: true },
|
|
174
|
-
);
|
|
168
|
+
await ctx.runjs(code, undefined, { preprocessTemplates: true });
|
|
175
169
|
});
|
|
176
170
|
},
|
|
177
171
|
},
|
|
@@ -286,16 +280,7 @@ return currentUsername;
|
|
|
286
280
|
const code = ctx?.inputArgs?.preview?.code || '';
|
|
287
281
|
ctx.onRefReady(ctx.ref, async (el) => {
|
|
288
282
|
ctx.defineProperty('element', { get: () => new ElementProxy(el as any) });
|
|
289
|
-
|
|
290
|
-
await ctx.runjs(
|
|
291
|
-
code,
|
|
292
|
-
{
|
|
293
|
-
window: createSafeWindow({ navigator }),
|
|
294
|
-
document: createSafeDocument(),
|
|
295
|
-
navigator,
|
|
296
|
-
},
|
|
297
|
-
{ preprocessTemplates: true },
|
|
298
|
-
);
|
|
283
|
+
await ctx.runjs(code, undefined, { preprocessTemplates: true });
|
|
299
284
|
});
|
|
300
285
|
},
|
|
301
286
|
},
|
|
@@ -9,14 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
import { useCallback, useRef, useState } from 'react';
|
|
11
11
|
import { parseErrorLineColumn, WRAPPER_PRELUDE_LINES } from '../errorHelpers';
|
|
12
|
-
import {
|
|
13
|
-
FlowModelContext,
|
|
14
|
-
JSRunner,
|
|
15
|
-
createSafeWindow,
|
|
16
|
-
createSafeDocument,
|
|
17
|
-
createSafeNavigator,
|
|
18
|
-
prepareRunJsCode,
|
|
19
|
-
} from '@nocobase/flow-engine';
|
|
12
|
+
import { FlowModelContext, JSRunner, prepareRunJsCode } from '@nocobase/flow-engine';
|
|
20
13
|
|
|
21
14
|
export type RunLog = { level: 'log' | 'info' | 'warn' | 'error'; msg: string; line?: number; column?: number };
|
|
22
15
|
type RunResult = Awaited<ReturnType<JSRunner['run']>>;
|
|
@@ -286,12 +279,7 @@ export function useCodeRunner(hostCtx: FlowModelContext, version = 'v1') {
|
|
|
286
279
|
const eventName = typeof onDef === 'string' ? onDef : onDef?.eventName;
|
|
287
280
|
if (!flow) {
|
|
288
281
|
// 无可用流程(典型场景:联动规则里的 RunJS 预览),直接在当前上下文执行代码
|
|
289
|
-
|
|
290
|
-
await directRunCtx.runjs(
|
|
291
|
-
code,
|
|
292
|
-
{ window: createSafeWindow({ navigator }), document: createSafeDocument(), navigator },
|
|
293
|
-
{ version },
|
|
294
|
-
);
|
|
282
|
+
await directRunCtx.runjs(code, undefined, { version });
|
|
295
283
|
} else if (typeof eventName === 'string') {
|
|
296
284
|
await m.dispatchEvent(eventName, { preview: { code } }, { sequential: true, useCache: false });
|
|
297
285
|
} else if (isManual) {
|
|
@@ -20,10 +20,8 @@ import * as acornWalk from 'acorn-walk';
|
|
|
20
20
|
import {
|
|
21
21
|
JSRunner,
|
|
22
22
|
FlowContext,
|
|
23
|
-
createSafeDocument,
|
|
24
|
-
createSafeNavigator,
|
|
25
|
-
createSafeWindow,
|
|
26
23
|
prepareRunJsCode,
|
|
24
|
+
RUNJS_ALLOWED_BARE_GLOBAL_NAMES,
|
|
27
25
|
shouldPreprocessRunJSTemplates,
|
|
28
26
|
} from '@nocobase/flow-engine';
|
|
29
27
|
|
|
@@ -604,41 +602,7 @@ function collectHeuristicIssues(code: string): RunJSIssue[] {
|
|
|
604
602
|
return issues;
|
|
605
603
|
}
|
|
606
604
|
|
|
607
|
-
const declared = new Set<string>(
|
|
608
|
-
// Common globals / allowed runtime context roots
|
|
609
|
-
'ctx',
|
|
610
|
-
'console',
|
|
611
|
-
'window',
|
|
612
|
-
'document',
|
|
613
|
-
'navigator',
|
|
614
|
-
'Math',
|
|
615
|
-
'Date',
|
|
616
|
-
'Array',
|
|
617
|
-
'Object',
|
|
618
|
-
'Number',
|
|
619
|
-
'String',
|
|
620
|
-
'Boolean',
|
|
621
|
-
'Promise',
|
|
622
|
-
'RegExp',
|
|
623
|
-
'Set',
|
|
624
|
-
'Map',
|
|
625
|
-
'WeakSet',
|
|
626
|
-
'WeakMap',
|
|
627
|
-
'JSON',
|
|
628
|
-
'Intl',
|
|
629
|
-
'URL',
|
|
630
|
-
'Error',
|
|
631
|
-
'TypeError',
|
|
632
|
-
'encodeURIComponent',
|
|
633
|
-
'decodeURIComponent',
|
|
634
|
-
'parseInt',
|
|
635
|
-
'parseFloat',
|
|
636
|
-
'isNaN',
|
|
637
|
-
'isFinite',
|
|
638
|
-
'undefined',
|
|
639
|
-
'NaN',
|
|
640
|
-
'Infinity',
|
|
641
|
-
]);
|
|
605
|
+
const declared = new Set<string>(RUNJS_ALLOWED_BARE_GLOBAL_NAMES);
|
|
642
606
|
|
|
643
607
|
const addId = (id: any) => {
|
|
644
608
|
if (id && typeof id.name === 'string') declared.add(id.name);
|
|
@@ -1036,15 +1000,14 @@ export async function diagnoseRunJS(
|
|
|
1036
1000
|
const { consoleCapture, loggerCapture } = createLogCollectors(logs);
|
|
1037
1001
|
|
|
1038
1002
|
const baseGlobals: Record<string, any> = { console: consoleCapture };
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1003
|
+
if (typeof window !== 'undefined') {
|
|
1004
|
+
baseGlobals.window = window;
|
|
1005
|
+
if (typeof navigator !== 'undefined') {
|
|
1042
1006
|
baseGlobals.navigator = navigator;
|
|
1043
|
-
baseGlobals.window = createSafeWindow({ navigator });
|
|
1044
|
-
baseGlobals.document = createSafeDocument();
|
|
1045
1007
|
}
|
|
1046
|
-
}
|
|
1047
|
-
|
|
1008
|
+
}
|
|
1009
|
+
if (typeof document !== 'undefined') {
|
|
1010
|
+
baseGlobals.document = document;
|
|
1048
1011
|
}
|
|
1049
1012
|
|
|
1050
1013
|
let prepared = src;
|
|
@@ -57,12 +57,11 @@ function getItemLabel(itemModel: any, t: (key: string) => string): string {
|
|
|
57
57
|
return '';
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
-
function
|
|
61
|
-
|
|
60
|
+
function splitFieldPath(path: string): string[] {
|
|
61
|
+
return String(path || '')
|
|
62
62
|
.split('.')
|
|
63
63
|
.map((s) => s.trim())
|
|
64
64
|
.filter(Boolean);
|
|
65
|
-
return segs.length ? segs[segs.length - 1] : '';
|
|
66
65
|
}
|
|
67
66
|
|
|
68
67
|
type CollectionLike = {
|
|
@@ -70,6 +69,15 @@ type CollectionLike = {
|
|
|
70
69
|
getFields?: () => unknown[];
|
|
71
70
|
};
|
|
72
71
|
|
|
72
|
+
type CollectionFieldLike = {
|
|
73
|
+
name?: unknown;
|
|
74
|
+
title?: unknown;
|
|
75
|
+
interface?: unknown;
|
|
76
|
+
target?: unknown;
|
|
77
|
+
isAssociationField?: () => boolean;
|
|
78
|
+
targetCollection?: CollectionLike | null;
|
|
79
|
+
};
|
|
80
|
+
|
|
73
81
|
function getCollectionFromFormBlockModel(model: any): CollectionLike | null {
|
|
74
82
|
if (!model || typeof model !== 'object') return null;
|
|
75
83
|
const collection = (model as any)?.collection || (model as any)?.context?.collection;
|
|
@@ -89,14 +97,7 @@ export function buildFieldAssignCascaderOptionsFromCollection(
|
|
|
89
97
|
const out: FieldAssignCascaderOption[] = [];
|
|
90
98
|
for (const rawField of fields) {
|
|
91
99
|
if (!rawField) continue;
|
|
92
|
-
const f = rawField as
|
|
93
|
-
name?: unknown;
|
|
94
|
-
title?: unknown;
|
|
95
|
-
interface?: unknown;
|
|
96
|
-
target?: unknown;
|
|
97
|
-
isAssociationField?: () => boolean;
|
|
98
|
-
targetCollection?: any;
|
|
99
|
-
};
|
|
100
|
+
const f = rawField as CollectionFieldLike;
|
|
100
101
|
const fieldInterface = typeof f.interface === 'string' ? f.interface : undefined;
|
|
101
102
|
if (!fieldInterface) continue;
|
|
102
103
|
if (fieldInterface === 'formula') continue;
|
|
@@ -123,23 +124,143 @@ export function buildFieldAssignCascaderOptionsFromCollection(
|
|
|
123
124
|
function mergeRootOptions(
|
|
124
125
|
configured: FieldAssignCascaderOption[],
|
|
125
126
|
allFields: FieldAssignCascaderOption[],
|
|
127
|
+
): FieldAssignCascaderOption[] {
|
|
128
|
+
return mergeCascaderOptions(configured, allFields);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function mergeCascaderOptions(
|
|
132
|
+
configured: FieldAssignCascaderOption[],
|
|
133
|
+
allFields: FieldAssignCascaderOption[],
|
|
126
134
|
): FieldAssignCascaderOption[] {
|
|
127
135
|
const configuredList = Array.isArray(configured) ? configured : [];
|
|
128
136
|
const allList = Array.isArray(allFields) ? allFields : [];
|
|
137
|
+
const result = configuredList.map((item) => cloneCascaderOption(item));
|
|
129
138
|
|
|
130
|
-
const
|
|
131
|
-
|
|
132
|
-
const v = it?.value ? String(it.value) : '';
|
|
133
|
-
if (v) configuredValues.add(v);
|
|
139
|
+
for (const item of allList) {
|
|
140
|
+
mergeCascaderOptionInto(result, item);
|
|
134
141
|
}
|
|
135
142
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
143
|
+
return result;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function cloneCascaderOption(option: FieldAssignCascaderOption): FieldAssignCascaderOption {
|
|
147
|
+
return {
|
|
148
|
+
...option,
|
|
149
|
+
children: option.children?.map((child) => cloneCascaderOption(child)),
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function mergeCascaderOptionInto(options: FieldAssignCascaderOption[], source: FieldAssignCascaderOption) {
|
|
154
|
+
const value = source?.value ? String(source.value) : '';
|
|
155
|
+
if (!value) return;
|
|
156
|
+
|
|
157
|
+
const existing = options.find((item) => String(item?.value || '') === value);
|
|
158
|
+
if (!existing) {
|
|
159
|
+
options.push(cloneCascaderOption(source));
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (!existing.label && source.label) {
|
|
164
|
+
existing.label = source.label;
|
|
165
|
+
}
|
|
166
|
+
if (source.isLeaf === false || existing.children?.length || source.children?.length) {
|
|
167
|
+
existing.isLeaf = false;
|
|
168
|
+
}
|
|
169
|
+
if (source.loading) {
|
|
170
|
+
existing.loading = source.loading;
|
|
171
|
+
}
|
|
172
|
+
if (source.children?.length) {
|
|
173
|
+
existing.children = mergeCascaderOptions(existing.children || [], source.children);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function getCollectionField(collection: CollectionLike | null, name: string): CollectionFieldLike | undefined {
|
|
178
|
+
const field = typeof collection?.getField === 'function' ? collection.getField(name) : undefined;
|
|
179
|
+
return field && typeof field === 'object' ? (field as CollectionFieldLike) : undefined;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function isAssociationFieldLike(field?: CollectionFieldLike | null) {
|
|
183
|
+
return !!(field?.isAssociationField?.() || field?.target || field?.targetCollection);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function getFieldLabelFromCollection(collection: CollectionLike | null, name: string, t: (key: string) => string) {
|
|
187
|
+
const field = getCollectionField(collection, name);
|
|
188
|
+
const title = typeof field?.title === 'string' && field.title ? field.title : name;
|
|
189
|
+
return t(title);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function getAssociationDepthFromFieldPath(
|
|
193
|
+
rootCollection: CollectionLike | null,
|
|
194
|
+
segments: string[],
|
|
195
|
+
): number | undefined {
|
|
196
|
+
if (!rootCollection) return undefined;
|
|
197
|
+
|
|
198
|
+
let collection: CollectionLike | null = rootCollection;
|
|
199
|
+
let associationDepth = 0;
|
|
200
|
+
for (let index = 0; index < segments.length; index++) {
|
|
201
|
+
const field = getCollectionField(collection, segments[index]);
|
|
202
|
+
if (!field) return undefined;
|
|
203
|
+
|
|
204
|
+
if (isAssociationFieldLike(field)) {
|
|
205
|
+
associationDepth += 1;
|
|
206
|
+
collection = field.targetCollection || null;
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if (index < segments.length - 1) return undefined;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return associationDepth;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function shouldIncludeConfiguredFieldPath(options: {
|
|
217
|
+
segments: string[];
|
|
218
|
+
rootCollection: CollectionLike | null;
|
|
219
|
+
leafField?: CollectionFieldLike | null;
|
|
220
|
+
maxAssociationFieldDepth: number;
|
|
221
|
+
}) {
|
|
222
|
+
const resolvedAssociationDepth = getAssociationDepthFromFieldPath(options.rootCollection, options.segments);
|
|
223
|
+
if (typeof resolvedAssociationDepth === 'number') {
|
|
224
|
+
return resolvedAssociationDepth <= options.maxAssociationFieldDepth;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const leafIsAssociation = isAssociationFieldLike(options.leafField);
|
|
228
|
+
const maxPathLength = leafIsAssociation ? options.maxAssociationFieldDepth : options.maxAssociationFieldDepth + 1;
|
|
229
|
+
return options.segments.length <= maxPathLength;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function buildNestedOptionFromFieldPath(options: {
|
|
233
|
+
targetPath: string;
|
|
234
|
+
leaf: FieldAssignCascaderOption;
|
|
235
|
+
rootCollection: CollectionLike | null;
|
|
236
|
+
t: (key: string) => string;
|
|
237
|
+
}): FieldAssignCascaderOption {
|
|
238
|
+
const segments = splitFieldPath(options.targetPath);
|
|
239
|
+
if (segments.length <= 1) {
|
|
240
|
+
return options.leaf;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const build = (index: number, collection: CollectionLike | null): FieldAssignCascaderOption => {
|
|
244
|
+
const value = segments[index];
|
|
245
|
+
const isLast = index === segments.length - 1;
|
|
246
|
+
if (isLast) {
|
|
247
|
+
return {
|
|
248
|
+
...options.leaf,
|
|
249
|
+
value,
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const field = getCollectionField(collection, value);
|
|
254
|
+
const nextCollection = field?.targetCollection || null;
|
|
255
|
+
return {
|
|
256
|
+
label: getFieldLabelFromCollection(collection, value, options.t),
|
|
257
|
+
value,
|
|
258
|
+
isLeaf: false,
|
|
259
|
+
children: [build(index + 1, nextCollection)],
|
|
260
|
+
};
|
|
261
|
+
};
|
|
141
262
|
|
|
142
|
-
return
|
|
263
|
+
return build(0, options.rootCollection);
|
|
143
264
|
}
|
|
144
265
|
|
|
145
266
|
export function collectFieldAssignCascaderOptions(options: {
|
|
@@ -167,7 +288,8 @@ export function collectFieldAssignCascaderOptions(options: {
|
|
|
167
288
|
const targetPath = getItemFieldPath(item);
|
|
168
289
|
if (!targetPath) continue;
|
|
169
290
|
|
|
170
|
-
const
|
|
291
|
+
const segments = splitFieldPath(targetPath);
|
|
292
|
+
const seg = segments[segments.length - 1];
|
|
171
293
|
if (!seg) continue;
|
|
172
294
|
|
|
173
295
|
const node: FieldAssignCascaderOption = {
|
|
@@ -179,10 +301,16 @@ export function collectFieldAssignCascaderOptions(options: {
|
|
|
179
301
|
const childItems = fieldModel?.subModels?.grid?.subModels?.items;
|
|
180
302
|
const cf: (CollectionField & { target?: unknown; targetCollection?: unknown }) | undefined =
|
|
181
303
|
fieldModel?.context?.collectionField;
|
|
182
|
-
const isAssociation =
|
|
304
|
+
const isAssociation = isAssociationFieldLike(cf);
|
|
183
305
|
const hasTargetCollection = !!cf?.targetCollection;
|
|
184
|
-
|
|
185
|
-
|
|
306
|
+
if (
|
|
307
|
+
!shouldIncludeConfiguredFieldPath({
|
|
308
|
+
segments,
|
|
309
|
+
rootCollection,
|
|
310
|
+
leafField: cf,
|
|
311
|
+
maxAssociationFieldDepth,
|
|
312
|
+
})
|
|
313
|
+
) {
|
|
186
314
|
continue;
|
|
187
315
|
}
|
|
188
316
|
|
|
@@ -203,7 +331,7 @@ export function collectFieldAssignCascaderOptions(options: {
|
|
|
203
331
|
node.isLeaf = true;
|
|
204
332
|
}
|
|
205
333
|
}
|
|
206
|
-
out
|
|
334
|
+
mergeCascaderOptionInto(out, buildNestedOptionFromFieldPath({ targetPath, leaf: node, rootCollection, t }));
|
|
207
335
|
continue;
|
|
208
336
|
}
|
|
209
337
|
|
|
@@ -215,7 +343,7 @@ export function collectFieldAssignCascaderOptions(options: {
|
|
|
215
343
|
node.isLeaf = true;
|
|
216
344
|
}
|
|
217
345
|
|
|
218
|
-
out
|
|
346
|
+
mergeCascaderOptionInto(out, buildNestedOptionFromFieldPath({ targetPath, leaf: node, rootCollection, t }));
|
|
219
347
|
}
|
|
220
348
|
|
|
221
349
|
return out;
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import {
|
|
10
|
+
import { tExpr } from '@nocobase/flow-engine';
|
|
11
11
|
import type { ButtonProps } from 'antd/es/button';
|
|
12
12
|
import { CodeEditor } from '../../components/code-editor';
|
|
13
13
|
import { ActionModel } from '../base';
|
|
@@ -72,12 +72,7 @@ ctx.message.info('Hello JS action.');
|
|
|
72
72
|
},
|
|
73
73
|
async handler(ctx, params) {
|
|
74
74
|
const { code, version } = resolveRunJsParams(ctx, params);
|
|
75
|
-
|
|
76
|
-
await ctx.runjs(
|
|
77
|
-
code,
|
|
78
|
-
{ window: createSafeWindow({ navigator }), document: createSafeDocument(), navigator },
|
|
79
|
-
{ version },
|
|
80
|
-
);
|
|
75
|
+
await ctx.runjs(code, undefined, { version });
|
|
81
76
|
},
|
|
82
77
|
},
|
|
83
78
|
},
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import {
|
|
10
|
+
import { tExpr } from '@nocobase/flow-engine';
|
|
11
11
|
import type { ButtonProps } from 'antd/es/button';
|
|
12
12
|
import { CodeEditor } from '../../components/code-editor';
|
|
13
13
|
import { ActionModel, ActionSceneEnum } from '../base';
|
|
@@ -78,12 +78,7 @@ if (!rows.length) {
|
|
|
78
78
|
},
|
|
79
79
|
async handler(ctx, params) {
|
|
80
80
|
const { code, version } = resolveRunJsParams(ctx, params);
|
|
81
|
-
|
|
82
|
-
await ctx.runjs(
|
|
83
|
-
code,
|
|
84
|
-
{ window: createSafeWindow({ navigator }), document: createSafeDocument(), navigator },
|
|
85
|
-
{ version },
|
|
86
|
-
);
|
|
81
|
+
await ctx.runjs(code, undefined, { version });
|
|
87
82
|
},
|
|
88
83
|
},
|
|
89
84
|
},
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import { ElementProxy,
|
|
10
|
+
import { ElementProxy, tExpr } from '@nocobase/flow-engine';
|
|
11
11
|
import React from 'react';
|
|
12
12
|
import { CodeEditor } from '../../components/code-editor';
|
|
13
13
|
import { ActionModel, ActionSceneEnum, CollectionActionGroupModel, RecordActionGroupModel } from '../base';
|
|
@@ -137,12 +137,7 @@ JSItemActionModel.registerFlow({
|
|
|
137
137
|
ctx.defineProperty('element', {
|
|
138
138
|
get: () => new ElementProxy(element),
|
|
139
139
|
});
|
|
140
|
-
|
|
141
|
-
await ctx.runjs(
|
|
142
|
-
code,
|
|
143
|
-
{ window: createSafeWindow({ navigator }), document: createSafeDocument(), navigator },
|
|
144
|
-
{ version },
|
|
145
|
-
);
|
|
140
|
+
await ctx.runjs(code, undefined, { version });
|
|
146
141
|
});
|
|
147
142
|
},
|
|
148
143
|
},
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import {
|
|
10
|
+
import { tExpr } from '@nocobase/flow-engine';
|
|
11
11
|
import type { ButtonProps } from 'antd/es/button';
|
|
12
12
|
import { CodeEditor } from '../../components/code-editor';
|
|
13
13
|
import { ActionSceneEnum, RecordActionModel } from '../base';
|
|
@@ -78,12 +78,7 @@ if (!ctx.record) {
|
|
|
78
78
|
},
|
|
79
79
|
async handler(ctx, params) {
|
|
80
80
|
const { code, version } = resolveRunJsParams(ctx, params);
|
|
81
|
-
|
|
82
|
-
await ctx.runjs(
|
|
83
|
-
code,
|
|
84
|
-
{ window: createSafeWindow({ navigator }), document: createSafeDocument(), navigator },
|
|
85
|
-
{ version },
|
|
86
|
-
);
|
|
81
|
+
await ctx.runjs(code, undefined, { version });
|
|
87
82
|
},
|
|
88
83
|
},
|
|
89
84
|
},
|