@nocobase/flow-engine 2.0.0-beta.9 → 2.0.0
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/lib/BlockScopedFlowEngine.js +0 -1
- package/lib/FlowDefinition.d.ts +2 -0
- package/lib/JSRunner.d.ts +6 -0
- package/lib/JSRunner.js +32 -2
- package/lib/ViewScopedFlowEngine.js +3 -0
- package/lib/acl/Acl.js +13 -3
- package/lib/components/FlowContextSelector.js +155 -10
- package/lib/components/settings/wrappers/component/SwitchWithTitle.js +2 -1
- package/lib/components/settings/wrappers/contextual/DefaultSettingsIcon.js +76 -15
- package/lib/components/settings/wrappers/contextual/FlowsContextMenu.js +24 -4
- package/lib/components/settings/wrappers/contextual/StepSettingsDialog.js +5 -1
- package/lib/components/variables/VariableInput.js +9 -4
- package/lib/components/variables/VariableTag.js +46 -39
- package/lib/components/variables/utils.d.ts +7 -0
- package/lib/components/variables/utils.js +42 -2
- package/lib/data-source/index.d.ts +7 -27
- package/lib/data-source/index.js +81 -51
- package/lib/executor/FlowExecutor.d.ts +2 -1
- package/lib/executor/FlowExecutor.js +163 -22
- package/lib/flowContext.d.ts +230 -7
- package/lib/flowContext.js +2267 -148
- package/lib/flowEngine.d.ts +21 -0
- package/lib/flowEngine.js +56 -8
- package/lib/flowI18n.js +6 -4
- package/lib/flowSettings.js +17 -11
- package/lib/index.d.ts +7 -1
- package/lib/index.js +21 -0
- package/lib/locale/en-US.json +9 -2
- package/lib/locale/index.d.ts +14 -0
- package/lib/locale/zh-CN.json +8 -1
- package/lib/models/CollectionFieldModel.d.ts +1 -0
- package/lib/models/CollectionFieldModel.js +3 -2
- package/lib/models/flowModel.js +12 -1
- package/lib/provider.js +5 -5
- package/lib/resources/baseRecordResource.d.ts +5 -0
- package/lib/resources/baseRecordResource.js +24 -0
- package/lib/resources/multiRecordResource.d.ts +1 -0
- package/lib/resources/multiRecordResource.js +11 -4
- package/lib/resources/singleRecordResource.js +2 -0
- package/lib/resources/sqlResource.d.ts +4 -3
- package/lib/resources/sqlResource.js +8 -3
- package/lib/runjs-context/contexts/FormJSFieldItemRunJSContext.js +12 -2
- package/lib/runjs-context/contexts/JSBlockRunJSContext.js +2 -2
- package/lib/runjs-context/contexts/JSEditableFieldRunJSContext.d.ts +16 -0
- package/lib/runjs-context/contexts/JSEditableFieldRunJSContext.js +125 -0
- package/lib/runjs-context/contexts/JSItemRunJSContext.js +12 -2
- package/lib/runjs-context/contexts/base.js +706 -41
- package/lib/runjs-context/contributions.d.ts +33 -0
- package/lib/runjs-context/contributions.js +88 -0
- package/lib/runjs-context/helpers.js +12 -1
- package/lib/runjs-context/setup.js +6 -0
- package/lib/runjs-context/snippets/global/api-request.snippet.js +3 -3
- package/lib/runjs-context/snippets/global/import-esm.snippet.js +2 -3
- package/lib/runjs-context/snippets/global/query-selector.snippet.js +8 -3
- package/lib/runjs-context/snippets/global/require-amd.snippet.js +1 -1
- package/lib/runjs-context/snippets/index.d.ts +11 -1
- package/lib/runjs-context/snippets/index.js +61 -40
- package/lib/runjs-context/snippets/scene/block/add-event-listener.snippet.js +10 -7
- package/lib/runjs-context/snippets/scene/block/api-fetch-render-list.snippet.js +3 -3
- package/lib/runjs-context/snippets/scene/block/chartjs-bar.snippet.js +2 -2
- package/lib/runjs-context/snippets/scene/block/echarts-init.snippet.js +2 -2
- package/lib/runjs-context/snippets/scene/block/render-iframe.snippet.js +2 -2
- package/lib/runjs-context/snippets/scene/block/render-react.snippet.js +1 -1
- package/lib/runjs-context/snippets/scene/block/render-statistics.snippet.js +1 -1
- package/lib/runjs-context/snippets/scene/block/render-timeline.snippet.js +1 -1
- package/lib/runjs-context/snippets/scene/block/resource-example.snippet.js +5 -5
- package/lib/runjs-context/snippets/scene/block/three-users-orbit.snippet.js +6 -6
- package/lib/runjs-context/snippets/scene/block/vue-component.snippet.js +3 -4
- package/lib/runjs-context/snippets/scene/detail/color-by-value.snippet.js +1 -1
- package/lib/runjs-context/snippets/scene/detail/copy-to-clipboard.snippet.js +20 -3
- package/lib/runjs-context/snippets/scene/detail/format-number.snippet.js +1 -1
- package/lib/runjs-context/snippets/scene/detail/innerHTML-value.snippet.js +1 -1
- package/lib/runjs-context/snippets/scene/detail/percentage-bar.snippet.js +3 -3
- package/lib/runjs-context/snippets/scene/detail/relative-time.snippet.js +3 -3
- package/lib/runjs-context/snippets/scene/detail/status-tag.snippet.js +2 -2
- package/lib/runjs-context/snippets/scene/form/cascade-select.snippet.js +1 -1
- package/lib/runjs-context/snippets/scene/form/render-basic.snippet.js +2 -2
- package/lib/runjs-context/snippets/scene/table/cell-open-dialog.snippet.js +6 -3
- package/lib/runjs-context/snippets/scene/table/concat-fields.snippet.js +3 -1
- package/lib/runjsLibs.d.ts +28 -0
- package/lib/runjsLibs.js +532 -0
- package/lib/scheduler/ModelOperationScheduler.d.ts +2 -0
- package/lib/scheduler/ModelOperationScheduler.js +25 -21
- package/lib/types.d.ts +27 -0
- package/lib/utils/associationObjectVariable.d.ts +2 -2
- package/lib/utils/createCollectionContextMeta.js +1 -0
- package/lib/utils/createEphemeralContext.js +2 -2
- package/lib/utils/dateVariable.d.ts +16 -0
- package/lib/utils/dateVariable.js +380 -0
- package/lib/utils/exceptions.d.ts +7 -0
- package/lib/utils/exceptions.js +10 -0
- package/lib/utils/index.d.ts +8 -3
- package/lib/utils/index.js +45 -0
- package/lib/utils/params-resolvers.js +16 -9
- package/lib/utils/resolveModuleUrl.d.ts +58 -0
- package/lib/utils/resolveModuleUrl.js +65 -0
- package/lib/utils/resolveRunJSObjectValues.d.ts +16 -0
- package/lib/utils/resolveRunJSObjectValues.js +61 -0
- package/lib/utils/runjsModuleLoader.d.ts +58 -0
- package/lib/utils/runjsModuleLoader.js +422 -0
- package/lib/utils/runjsTemplateCompat.d.ts +35 -0
- package/lib/utils/runjsTemplateCompat.js +743 -0
- package/lib/utils/runjsValue.d.ts +29 -0
- package/lib/utils/runjsValue.js +275 -0
- package/lib/utils/safeGlobals.d.ts +18 -8
- package/lib/utils/safeGlobals.js +164 -17
- package/lib/utils/schema-utils.d.ts +10 -0
- package/lib/utils/schema-utils.js +61 -0
- package/lib/views/createViewMeta.d.ts +0 -7
- package/lib/views/createViewMeta.js +19 -70
- package/lib/views/index.d.ts +1 -2
- package/lib/views/index.js +4 -3
- package/lib/views/useDialog.js +7 -2
- package/lib/views/useDrawer.js +7 -2
- package/lib/views/usePage.d.ts +4 -0
- package/lib/views/usePage.js +43 -6
- package/lib/views/usePopover.js +4 -1
- package/lib/views/viewEvents.d.ts +17 -0
- package/lib/views/viewEvents.js +90 -0
- package/package.json +4 -4
- package/src/BlockScopedFlowEngine.ts +2 -5
- package/src/JSRunner.ts +44 -2
- package/src/ViewScopedFlowEngine.ts +4 -0
- package/src/__tests__/JSRunner.test.ts +64 -0
- package/src/__tests__/createViewMeta.popup.test.ts +62 -1
- package/src/__tests__/flowContext.test.ts +693 -1
- package/src/__tests__/flowEngine.dataSourceDirty.test.ts +63 -0
- package/src/__tests__/flowModel.openView.navigation.test.ts +28 -0
- package/src/__tests__/flowRunJSContextDefine.test.ts +63 -0
- package/src/__tests__/flowRuntimeContext.test.ts +2 -1
- package/src/__tests__/flowSettings.open.test.tsx +123 -19
- package/src/__tests__/runjsContext.test.ts +10 -7
- package/src/__tests__/runjsContextImplementations.test.ts +34 -3
- package/src/__tests__/runjsContextRuntime.test.ts +3 -3
- package/src/__tests__/runjsContributions.test.ts +89 -0
- package/src/__tests__/runjsExternalLibs.test.ts +242 -0
- package/src/__tests__/runjsLibsLazyLoading.test.ts +44 -0
- package/src/__tests__/runjsLocales.test.ts +4 -1
- package/src/__tests__/runjsPreprocessDefault.test.ts +49 -0
- package/src/__tests__/runjsRuntimeFeatures.test.ts +166 -0
- package/src/__tests__/runjsSnippets.test.ts +40 -3
- package/src/acl/Acl.tsx +3 -3
- package/src/components/FlowContextSelector.tsx +208 -12
- package/src/components/settings/wrappers/component/SwitchWithTitle.tsx +2 -1
- package/src/components/settings/wrappers/component/__tests__/InlineControls.test.tsx +74 -0
- package/src/components/settings/wrappers/contextual/DefaultSettingsIcon.tsx +109 -16
- package/src/components/settings/wrappers/contextual/FlowsContextMenu.tsx +41 -7
- package/src/components/settings/wrappers/contextual/StepSettingsDialog.tsx +13 -2
- package/src/components/settings/wrappers/contextual/__tests__/DefaultSettingsIcon.test.tsx +157 -5
- package/src/components/variables/VariableInput.tsx +12 -4
- package/src/components/variables/VariableTag.tsx +54 -45
- package/src/components/variables/__tests__/FlowContextSelector.test.tsx +260 -3
- package/src/components/variables/__tests__/VariableTag.test.tsx +50 -0
- package/src/components/variables/__tests__/utils.test.ts +81 -3
- package/src/components/variables/utils.ts +67 -6
- package/src/data-source/index.ts +85 -110
- package/src/executor/FlowExecutor.ts +200 -23
- package/src/executor/__tests__/flowExecutor.test.ts +66 -0
- package/src/flowContext.ts +2986 -211
- package/src/flowEngine.ts +59 -8
- package/src/flowI18n.ts +7 -5
- package/src/flowSettings.ts +18 -12
- package/src/index.ts +14 -1
- package/src/locale/en-US.json +9 -2
- package/src/locale/zh-CN.json +8 -1
- package/src/models/CollectionFieldModel.tsx +3 -1
- package/src/models/__tests__/dispatchEvent.when.test.ts +554 -0
- package/src/models/__tests__/flowModel.test.ts +20 -4
- package/src/models/flowModel.tsx +13 -1
- package/src/provider.tsx +7 -6
- package/src/resources/__tests__/multiRecordResource.test.ts +44 -0
- package/src/resources/__tests__/sqlResource.test.ts +60 -0
- package/src/resources/baseRecordResource.ts +31 -0
- package/src/resources/multiRecordResource.ts +11 -4
- package/src/resources/singleRecordResource.ts +3 -0
- package/src/resources/sqlResource.ts +11 -6
- package/src/runjs-context/contexts/FormJSFieldItemRunJSContext.ts +10 -0
- package/src/runjs-context/contexts/JSBlockRunJSContext.ts +6 -2
- package/src/runjs-context/contexts/JSEditableFieldRunJSContext.ts +106 -0
- package/src/runjs-context/contexts/JSItemRunJSContext.ts +10 -0
- package/src/runjs-context/contexts/base.ts +715 -44
- package/src/runjs-context/contributions.ts +88 -0
- package/src/runjs-context/helpers.ts +11 -1
- package/src/runjs-context/setup.ts +6 -0
- package/src/runjs-context/snippets/global/api-request.snippet.ts +3 -3
- package/src/runjs-context/snippets/global/import-esm.snippet.ts +2 -3
- package/src/runjs-context/snippets/global/query-selector.snippet.ts +8 -3
- package/src/runjs-context/snippets/global/require-amd.snippet.ts +1 -1
- package/src/runjs-context/snippets/index.ts +75 -41
- package/src/runjs-context/snippets/scene/block/add-event-listener.snippet.ts +11 -13
- package/src/runjs-context/snippets/scene/block/api-fetch-render-list.snippet.ts +3 -3
- package/src/runjs-context/snippets/scene/block/chartjs-bar.snippet.ts +2 -2
- package/src/runjs-context/snippets/scene/block/echarts-init.snippet.ts +2 -2
- package/src/runjs-context/snippets/scene/block/render-iframe.snippet.ts +2 -2
- package/src/runjs-context/snippets/scene/block/render-react.snippet.ts +1 -1
- package/src/runjs-context/snippets/scene/block/render-statistics.snippet.ts +1 -1
- package/src/runjs-context/snippets/scene/block/render-timeline.snippet.ts +1 -1
- package/src/runjs-context/snippets/scene/block/resource-example.snippet.ts +6 -11
- package/src/runjs-context/snippets/scene/block/three-users-orbit.snippet.ts +6 -6
- package/src/runjs-context/snippets/scene/block/vue-component.snippet.ts +3 -4
- package/src/runjs-context/snippets/scene/detail/color-by-value.snippet.ts +1 -1
- package/src/runjs-context/snippets/scene/detail/copy-to-clipboard.snippet.ts +20 -3
- package/src/runjs-context/snippets/scene/detail/format-number.snippet.ts +1 -1
- package/src/runjs-context/snippets/scene/detail/innerHTML-value.snippet.ts +1 -1
- package/src/runjs-context/snippets/scene/detail/percentage-bar.snippet.ts +3 -3
- package/src/runjs-context/snippets/scene/detail/relative-time.snippet.ts +3 -3
- package/src/runjs-context/snippets/scene/detail/status-tag.snippet.ts +2 -2
- package/src/runjs-context/snippets/scene/form/cascade-select.snippet.ts +1 -1
- package/src/runjs-context/snippets/scene/form/render-basic.snippet.ts +3 -8
- package/src/runjs-context/snippets/scene/table/cell-open-dialog.snippet.ts +6 -3
- package/src/runjs-context/snippets/scene/table/concat-fields.snippet.ts +3 -1
- package/src/runjsLibs.ts +622 -0
- package/src/scheduler/ModelOperationScheduler.ts +27 -21
- package/src/types.ts +38 -1
- package/src/utils/__tests__/dateVariable.test.ts +101 -0
- package/src/utils/__tests__/params-resolvers.test.ts +40 -0
- package/src/utils/__tests__/runjsRequireAsyncAutoWhitelist.test.ts +38 -0
- package/src/utils/__tests__/runjsTemplateCompat.test.ts +159 -0
- package/src/utils/__tests__/runjsValue.test.ts +44 -0
- package/src/utils/__tests__/safeGlobals.test.ts +57 -2
- package/src/utils/__tests__/utils.test.ts +95 -0
- package/src/utils/associationObjectVariable.ts +2 -2
- package/src/utils/createCollectionContextMeta.ts +1 -0
- package/src/utils/createEphemeralContext.ts +5 -4
- package/src/utils/dateVariable.ts +397 -0
- package/src/utils/exceptions.ts +11 -0
- package/src/utils/index.ts +37 -3
- package/src/utils/params-resolvers.ts +23 -9
- package/src/utils/resolveModuleUrl.ts +91 -0
- package/src/utils/resolveRunJSObjectValues.ts +46 -0
- package/src/utils/runjsModuleLoader.ts +553 -0
- package/src/utils/runjsTemplateCompat.ts +828 -0
- package/src/utils/runjsValue.ts +287 -0
- package/src/utils/safeGlobals.ts +188 -17
- package/src/utils/schema-utils.ts +79 -0
- package/src/views/__tests__/FlowView.usePage.test.tsx +54 -1
- package/src/views/__tests__/useDialog.closeDestroy.test.tsx +35 -8
- package/src/views/__tests__/viewEvents.resolveOpenerEngine.test.ts +28 -0
- package/src/views/createViewMeta.ts +22 -75
- package/src/views/index.tsx +1 -2
- package/src/views/useDialog.tsx +8 -1
- package/src/views/useDrawer.tsx +8 -1
- package/src/views/usePage.tsx +51 -5
- package/src/views/usePopover.tsx +4 -1
- package/src/views/viewEvents.ts +55 -0
|
@@ -0,0 +1,287 @@
|
|
|
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
|
+
export type RunJSValue = {
|
|
11
|
+
code: string;
|
|
12
|
+
version?: string;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const RUNJS_ALLOWED_KEYS = new Set(['code', 'version']);
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Strictly detect RunJSValue to avoid conflicting with normal constant objects.
|
|
19
|
+
* - MUST be a plain object (not array)
|
|
20
|
+
* - MUST include string `code`
|
|
21
|
+
* - MAY include string `version`
|
|
22
|
+
* - MUST NOT include any other enumerable keys
|
|
23
|
+
*/
|
|
24
|
+
export function isRunJSValue(value: any): value is RunJSValue {
|
|
25
|
+
if (!value || typeof value !== 'object') return false;
|
|
26
|
+
if (Array.isArray(value)) return false;
|
|
27
|
+
const keys = Object.keys(value);
|
|
28
|
+
if (!keys.includes('code')) return false;
|
|
29
|
+
if (typeof (value as any).code !== 'string') return false;
|
|
30
|
+
if ('version' in value && (value as any).version != null && typeof (value as any).version !== 'string') return false;
|
|
31
|
+
for (const k of keys) {
|
|
32
|
+
if (!RUNJS_ALLOWED_KEYS.has(k)) return false;
|
|
33
|
+
}
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function normalizeRunJSValue(value: RunJSValue): Required<RunJSValue> {
|
|
38
|
+
return {
|
|
39
|
+
code: String(value?.code ?? ''),
|
|
40
|
+
version: String(value?.version ?? 'v1'),
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function stripStringsAndComments(code: string): string {
|
|
45
|
+
// Keep template literals untouched (may include ${} expressions).
|
|
46
|
+
let out = '';
|
|
47
|
+
let state: 'code' | 'single' | 'double' | 'line' | 'block' = 'code';
|
|
48
|
+
for (let i = 0; i < code.length; i++) {
|
|
49
|
+
const ch = code[i];
|
|
50
|
+
const next = i + 1 < code.length ? code[i + 1] : '';
|
|
51
|
+
|
|
52
|
+
if (state === 'code') {
|
|
53
|
+
if (ch === '/' && next === '/') {
|
|
54
|
+
out += ' ';
|
|
55
|
+
i++;
|
|
56
|
+
state = 'line';
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
if (ch === '/' && next === '*') {
|
|
60
|
+
out += ' ';
|
|
61
|
+
i++;
|
|
62
|
+
state = 'block';
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
if (ch === "'") {
|
|
66
|
+
out += ' ';
|
|
67
|
+
state = 'single';
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
if (ch === '"') {
|
|
71
|
+
out += ' ';
|
|
72
|
+
state = 'double';
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
out += ch;
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (state === 'line') {
|
|
80
|
+
if (ch === '\n') {
|
|
81
|
+
out += '\n';
|
|
82
|
+
state = 'code';
|
|
83
|
+
} else {
|
|
84
|
+
out += ' ';
|
|
85
|
+
}
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (state === 'block') {
|
|
90
|
+
if (ch === '*' && next === '/') {
|
|
91
|
+
out += ' ';
|
|
92
|
+
i++;
|
|
93
|
+
state = 'code';
|
|
94
|
+
} else {
|
|
95
|
+
out += ch === '\n' ? '\n' : ' ';
|
|
96
|
+
}
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (state === 'single') {
|
|
101
|
+
if (ch === '\\') {
|
|
102
|
+
out += ' ';
|
|
103
|
+
i++;
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
if (ch === "'") {
|
|
107
|
+
out += ' ';
|
|
108
|
+
state = 'code';
|
|
109
|
+
} else {
|
|
110
|
+
out += ch === '\n' ? '\n' : ' ';
|
|
111
|
+
}
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// state === 'double'
|
|
116
|
+
if (ch === '\\') {
|
|
117
|
+
out += ' ';
|
|
118
|
+
i++;
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
if (ch === '"') {
|
|
122
|
+
out += ' ';
|
|
123
|
+
state = 'code';
|
|
124
|
+
} else {
|
|
125
|
+
out += ch === '\n' ? '\n' : ' ';
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return out;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function stripComments(code: string): string {
|
|
132
|
+
let out = '';
|
|
133
|
+
let state: 'code' | 'single' | 'double' | 'line' | 'block' = 'code';
|
|
134
|
+
for (let i = 0; i < code.length; i++) {
|
|
135
|
+
const ch = code[i];
|
|
136
|
+
const next = i + 1 < code.length ? code[i + 1] : '';
|
|
137
|
+
|
|
138
|
+
if (state === 'code') {
|
|
139
|
+
if (ch === '/' && next === '/') {
|
|
140
|
+
out += ' ';
|
|
141
|
+
i++;
|
|
142
|
+
state = 'line';
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
if (ch === '/' && next === '*') {
|
|
146
|
+
out += ' ';
|
|
147
|
+
i++;
|
|
148
|
+
state = 'block';
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
if (ch === "'") {
|
|
152
|
+
out += ch;
|
|
153
|
+
state = 'single';
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
if (ch === '"') {
|
|
157
|
+
out += ch;
|
|
158
|
+
state = 'double';
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
out += ch;
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (state === 'line') {
|
|
166
|
+
if (ch === '\n') {
|
|
167
|
+
out += '\n';
|
|
168
|
+
state = 'code';
|
|
169
|
+
} else {
|
|
170
|
+
out += ' ';
|
|
171
|
+
}
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (state === 'block') {
|
|
176
|
+
if (ch === '*' && next === '/') {
|
|
177
|
+
out += ' ';
|
|
178
|
+
i++;
|
|
179
|
+
state = 'code';
|
|
180
|
+
} else {
|
|
181
|
+
out += ch === '\n' ? '\n' : ' ';
|
|
182
|
+
}
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (state === 'single') {
|
|
187
|
+
out += ch;
|
|
188
|
+
if (ch === '\\') {
|
|
189
|
+
const nextCh = i + 1 < code.length ? code[i + 1] : '';
|
|
190
|
+
if (nextCh) {
|
|
191
|
+
out += nextCh;
|
|
192
|
+
i++;
|
|
193
|
+
}
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
if (ch === "'") {
|
|
197
|
+
state = 'code';
|
|
198
|
+
}
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// state === 'double'
|
|
203
|
+
out += ch;
|
|
204
|
+
if (ch === '\\') {
|
|
205
|
+
const nextCh = i + 1 < code.length ? code[i + 1] : '';
|
|
206
|
+
if (nextCh) {
|
|
207
|
+
out += nextCh;
|
|
208
|
+
i++;
|
|
209
|
+
}
|
|
210
|
+
continue;
|
|
211
|
+
}
|
|
212
|
+
if (ch === '"') {
|
|
213
|
+
state = 'code';
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
return out;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function normalizeSubPath(raw: string): { subPath: string; wildcard: boolean } {
|
|
220
|
+
if (!raw) return { subPath: '', wildcard: false };
|
|
221
|
+
let s = raw;
|
|
222
|
+
// Convert simple string literal keys: ['a'] / ["a"] -> .a
|
|
223
|
+
s = s.replace(/\[['"]([a-zA-Z_$][a-zA-Z0-9_$]*)['"]\]/g, '.$1');
|
|
224
|
+
|
|
225
|
+
// Any remaining bracket access with non-numeric content is considered dynamic -> wildcard.
|
|
226
|
+
const bracketRe = /\[([^\]]+)\]/g;
|
|
227
|
+
let m: RegExpExecArray | null;
|
|
228
|
+
while ((m = bracketRe.exec(s))) {
|
|
229
|
+
const inner = String(m[1] ?? '').trim();
|
|
230
|
+
if (/^\d+$/.test(inner)) continue;
|
|
231
|
+
if (/^['"][a-zA-Z_$][a-zA-Z0-9_$]*['"]$/.test(inner)) continue;
|
|
232
|
+
return { subPath: s.startsWith('.') ? s.slice(1) : s, wildcard: true };
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (s.startsWith('.')) s = s.slice(1);
|
|
236
|
+
return { subPath: s, wildcard: false };
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Heuristic extraction of ctx variable usage from RunJS code.
|
|
241
|
+
*
|
|
242
|
+
* Returns a map: varName -> string[] subPaths
|
|
243
|
+
* - subPath '' means the variable root is used (or dependency is dynamic), caller MAY treat it as wildcard.
|
|
244
|
+
* - Only best-effort parsing; correctness prefers over-approximation.
|
|
245
|
+
*/
|
|
246
|
+
export function extractUsedVariablePathsFromRunJS(code: string): Record<string, string[]> {
|
|
247
|
+
if (typeof code !== 'string' || !code.trim()) return {};
|
|
248
|
+
const src = stripStringsAndComments(code);
|
|
249
|
+
const srcWithStrings = stripComments(code);
|
|
250
|
+
const usage = new Map<string, Set<string>>();
|
|
251
|
+
|
|
252
|
+
const add = (varName: string, subPath: string) => {
|
|
253
|
+
if (!varName) return;
|
|
254
|
+
const set = usage.get(varName) || new Set<string>();
|
|
255
|
+
set.add(subPath || '');
|
|
256
|
+
usage.set(varName, set);
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
// dot form: ctx.foo.bar / ctx.foo[0].bar (excluding ctx.method(...))
|
|
260
|
+
const dotRe = /ctx\.([a-zA-Z_$][a-zA-Z0-9_$]*(?:(?:\.[a-zA-Z_$][a-zA-Z0-9_$]*)|(?:\[[^\]]+\]))*)(?!\s*\()/g;
|
|
261
|
+
let match: RegExpExecArray | null;
|
|
262
|
+
while ((match = dotRe.exec(src))) {
|
|
263
|
+
const pathAfterCtx = match[1] || '';
|
|
264
|
+
const firstKeyMatch = pathAfterCtx.match(/^([a-zA-Z_$][a-zA-Z0-9_$]*)/);
|
|
265
|
+
if (!firstKeyMatch) continue;
|
|
266
|
+
const firstKey = firstKeyMatch[1];
|
|
267
|
+
const rest = pathAfterCtx.slice(firstKey.length);
|
|
268
|
+
const { subPath, wildcard } = normalizeSubPath(rest);
|
|
269
|
+
add(firstKey, wildcard ? '' : subPath);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// bracket root: ctx['foo'].bar / ctx["foo"][0] (excluding ctx['method'](...))
|
|
273
|
+
const bracketRootRe =
|
|
274
|
+
/ctx\s*\[\s*(['"])([a-zA-Z_$][a-zA-Z0-9_$]*)\1\s*\]((?:(?:\.[a-zA-Z_$][a-zA-Z0-9_$]*)|(?:\[[^\]]+\]))*)(?!\s*\()/g;
|
|
275
|
+
while ((match = bracketRootRe.exec(srcWithStrings))) {
|
|
276
|
+
const varName = match[2] || '';
|
|
277
|
+
const rest = match[3] || '';
|
|
278
|
+
const { subPath, wildcard } = normalizeSubPath(rest);
|
|
279
|
+
add(varName, wildcard ? '' : subPath);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const out: Record<string, string[]> = {};
|
|
283
|
+
for (const [k, set] of usage.entries()) {
|
|
284
|
+
out[k] = Array.from(set);
|
|
285
|
+
}
|
|
286
|
+
return out;
|
|
287
|
+
}
|
package/src/utils/safeGlobals.ts
CHANGED
|
@@ -9,12 +9,72 @@
|
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* 统一的安全全局对象代理:window/document/navigator
|
|
12
|
-
* - window:仅允许常用的定时器、console、Math、Date、FormData、addEventListener、open(安全包装)、location(安全代理)
|
|
12
|
+
* - window:仅允许常用的定时器、console、Math、Date、FormData、Blob、URL、addEventListener、open(安全包装)、location(安全代理)
|
|
13
13
|
* - document:仅允许 createElement/querySelector/querySelectorAll
|
|
14
14
|
* - navigator:仅提供极少量低风险能力(clipboard.writeText、onLine、language、languages)
|
|
15
15
|
* - 不允许随意访问未声明的属性,最小权限原则
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
|
+
type RunJSSafeGlobalsRegistry = {
|
|
19
|
+
windowAllow: Set<string>;
|
|
20
|
+
documentAllow: Set<string>;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
function getRunJSSafeGlobalsRegistry(): RunJSSafeGlobalsRegistry {
|
|
24
|
+
const g: any = globalThis as any;
|
|
25
|
+
if (g.__nocobaseRunJSSafeGlobalsRegistry?.windowAllow && g.__nocobaseRunJSSafeGlobalsRegistry?.documentAllow) {
|
|
26
|
+
return g.__nocobaseRunJSSafeGlobalsRegistry as RunJSSafeGlobalsRegistry;
|
|
27
|
+
}
|
|
28
|
+
const reg: RunJSSafeGlobalsRegistry = {
|
|
29
|
+
windowAllow: new Set<string>(),
|
|
30
|
+
documentAllow: new Set<string>(),
|
|
31
|
+
};
|
|
32
|
+
g.__nocobaseRunJSSafeGlobalsRegistry = reg;
|
|
33
|
+
return reg;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function registerRunJSSafeWindowGlobals(keys: Iterable<string> | null | undefined): void {
|
|
37
|
+
if (!keys) return;
|
|
38
|
+
const reg = getRunJSSafeGlobalsRegistry();
|
|
39
|
+
for (const k of keys) {
|
|
40
|
+
if (typeof k !== 'string') continue;
|
|
41
|
+
const key = k.trim();
|
|
42
|
+
if (!key) continue;
|
|
43
|
+
reg.windowAllow.add(key);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function registerRunJSSafeDocumentGlobals(keys: Iterable<string> | null | undefined): void {
|
|
48
|
+
if (!keys) return;
|
|
49
|
+
const reg = getRunJSSafeGlobalsRegistry();
|
|
50
|
+
for (const k of keys) {
|
|
51
|
+
if (typeof k !== 'string') continue;
|
|
52
|
+
const key = k.trim();
|
|
53
|
+
if (!key) continue;
|
|
54
|
+
reg.documentAllow.add(key);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function __resetRunJSSafeGlobalsRegistryForTests(): void {
|
|
59
|
+
const g: any = globalThis as any;
|
|
60
|
+
if (g.__nocobaseRunJSSafeGlobalsRegistry) {
|
|
61
|
+
try {
|
|
62
|
+
g.__nocobaseRunJSSafeGlobalsRegistry.windowAllow?.clear?.();
|
|
63
|
+
g.__nocobaseRunJSSafeGlobalsRegistry.documentAllow?.clear?.();
|
|
64
|
+
} catch {
|
|
65
|
+
// ignore
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function isAllowedDynamicWindowKey(key: string): boolean {
|
|
71
|
+
return getRunJSSafeGlobalsRegistry().windowAllow.has(key);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function isAllowedDynamicDocumentKey(key: string): boolean {
|
|
75
|
+
return getRunJSSafeGlobalsRegistry().documentAllow.has(key);
|
|
76
|
+
}
|
|
77
|
+
|
|
18
78
|
export function createSafeWindow(extra?: Record<string, any>) {
|
|
19
79
|
// 解析相对 URL 使用脱敏 base(不含 query/hash),避免在解析时泄露敏感信息
|
|
20
80
|
const getSafeBaseHref = () => `${window.location.origin}${window.location.pathname}`;
|
|
@@ -151,6 +211,8 @@ export function createSafeWindow(extra?: Record<string, any>) {
|
|
|
151
211
|
Math,
|
|
152
212
|
Date,
|
|
153
213
|
FormData,
|
|
214
|
+
...(typeof Blob !== 'undefined' ? { Blob } : {}),
|
|
215
|
+
...(typeof URL !== 'undefined' ? { URL } : {}),
|
|
154
216
|
// 事件侦听仅绑定到真实 window,便于少量需要的全局监听
|
|
155
217
|
addEventListener: addEventListener.bind(window),
|
|
156
218
|
// 安全的 window.open 代理
|
|
@@ -160,15 +222,44 @@ export function createSafeWindow(extra?: Record<string, any>) {
|
|
|
160
222
|
...(extra || {}),
|
|
161
223
|
};
|
|
162
224
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
}
|
|
225
|
+
const target: Record<string, any> = Object.create(null);
|
|
226
|
+
|
|
227
|
+
return new Proxy(target, {
|
|
228
|
+
get(t, prop: string | symbol) {
|
|
229
|
+
if (typeof prop !== 'string') {
|
|
230
|
+
return Reflect.get(t, prop);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
if (prop in allowedGlobals) return allowedGlobals[prop];
|
|
234
|
+
if (Object.prototype.hasOwnProperty.call(t, prop)) return (t as any)[prop];
|
|
235
|
+
if (isAllowedDynamicWindowKey(prop)) {
|
|
236
|
+
const v = (window as any)[prop];
|
|
237
|
+
// Bind functions to the real window to avoid Illegal invocation
|
|
238
|
+
if (typeof v === 'function') return v.bind(window);
|
|
239
|
+
return v;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
throw new Error(`Access to global property "${prop}" is not allowed.`);
|
|
170
243
|
},
|
|
171
|
-
|
|
244
|
+
set(t, prop: string | symbol, value: any) {
|
|
245
|
+
if (typeof prop !== 'string') {
|
|
246
|
+
Reflect.set(t, prop, value);
|
|
247
|
+
return true;
|
|
248
|
+
}
|
|
249
|
+
if (prop in allowedGlobals) {
|
|
250
|
+
throw new Error(`Mutation of global property "${prop}" is not allowed.`);
|
|
251
|
+
}
|
|
252
|
+
(t as any)[prop] = value;
|
|
253
|
+
return true;
|
|
254
|
+
},
|
|
255
|
+
has(t, prop: string | symbol) {
|
|
256
|
+
if (typeof prop !== 'string') return Reflect.has(t, prop);
|
|
257
|
+
if (prop in allowedGlobals) return true;
|
|
258
|
+
if (Object.prototype.hasOwnProperty.call(t, prop)) return true;
|
|
259
|
+
if (isAllowedDynamicWindowKey(prop)) return true;
|
|
260
|
+
return false;
|
|
261
|
+
},
|
|
262
|
+
});
|
|
172
263
|
}
|
|
173
264
|
|
|
174
265
|
export function createSafeDocument(extra?: Record<string, any>) {
|
|
@@ -178,15 +269,43 @@ export function createSafeDocument(extra?: Record<string, any>) {
|
|
|
178
269
|
querySelectorAll: document.querySelectorAll.bind(document),
|
|
179
270
|
...(extra || {}),
|
|
180
271
|
};
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
{
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
272
|
+
const target: Record<string, any> = Object.create(null);
|
|
273
|
+
return new Proxy(target, {
|
|
274
|
+
get(t, prop: string | symbol) {
|
|
275
|
+
if (typeof prop !== 'string') {
|
|
276
|
+
return Reflect.get(t, prop);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
if (prop in allowed) return allowed[prop];
|
|
280
|
+
if (Object.prototype.hasOwnProperty.call(t, prop)) return (t as any)[prop];
|
|
281
|
+
if (isAllowedDynamicDocumentKey(prop)) {
|
|
282
|
+
const v = (document as any)[prop];
|
|
283
|
+
// Bind functions to the real document to avoid Illegal invocation
|
|
284
|
+
if (typeof v === 'function') return v.bind(document);
|
|
285
|
+
return v;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
throw new Error(`Access to document property "${prop}" is not allowed.`);
|
|
188
289
|
},
|
|
189
|
-
|
|
290
|
+
set(t, prop: string | symbol, value: any) {
|
|
291
|
+
if (typeof prop !== 'string') {
|
|
292
|
+
Reflect.set(t, prop, value);
|
|
293
|
+
return true;
|
|
294
|
+
}
|
|
295
|
+
if (prop in allowed) {
|
|
296
|
+
throw new Error(`Mutation of document property "${prop}" is not allowed.`);
|
|
297
|
+
}
|
|
298
|
+
(t as any)[prop] = value;
|
|
299
|
+
return true;
|
|
300
|
+
},
|
|
301
|
+
has(t, prop: string | symbol) {
|
|
302
|
+
if (typeof prop !== 'string') return Reflect.has(t, prop);
|
|
303
|
+
if (prop in allowed) return true;
|
|
304
|
+
if (Object.prototype.hasOwnProperty.call(t, prop)) return true;
|
|
305
|
+
if (isAllowedDynamicDocumentKey(prop)) return true;
|
|
306
|
+
return false;
|
|
307
|
+
},
|
|
308
|
+
});
|
|
190
309
|
}
|
|
191
310
|
|
|
192
311
|
export function createSafeNavigator(extra?: Record<string, any>) {
|
|
@@ -233,3 +352,55 @@ export function createSafeNavigator(extra?: Record<string, any>) {
|
|
|
233
352
|
},
|
|
234
353
|
);
|
|
235
354
|
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Create a safe globals object for RunJS execution.
|
|
358
|
+
*
|
|
359
|
+
* - Always tries to provide `navigator`
|
|
360
|
+
* - Best-effort provides `window` and `document` in browser environments
|
|
361
|
+
* - Never throws (so callers can decide how to handle missing globals)
|
|
362
|
+
*/
|
|
363
|
+
export function createSafeRunJSGlobals(extraGlobals?: Record<string, any>): Record<string, any> {
|
|
364
|
+
const globals: Record<string, any> = {};
|
|
365
|
+
|
|
366
|
+
try {
|
|
367
|
+
const navigator = createSafeNavigator();
|
|
368
|
+
globals.navigator = navigator;
|
|
369
|
+
try {
|
|
370
|
+
globals.window = createSafeWindow({ navigator });
|
|
371
|
+
} catch {
|
|
372
|
+
// ignore when window is not available (e.g. SSR/tests)
|
|
373
|
+
}
|
|
374
|
+
} catch {
|
|
375
|
+
// ignore
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
try {
|
|
379
|
+
globals.document = createSafeDocument();
|
|
380
|
+
} catch {
|
|
381
|
+
// ignore when document is not available (e.g. SSR/tests)
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
return extraGlobals ? { ...globals, ...extraGlobals } : globals;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* Execute RunJS with safe globals (window/document/navigator).
|
|
389
|
+
*
|
|
390
|
+
* Keeps `this` binding by calling `ctx.runjs(...)` instead of passing bare function references.
|
|
391
|
+
*/
|
|
392
|
+
export async function runjsWithSafeGlobals(
|
|
393
|
+
ctx: unknown,
|
|
394
|
+
code: string,
|
|
395
|
+
options?: any,
|
|
396
|
+
extraGlobals?: Record<string, any>,
|
|
397
|
+
): Promise<any> {
|
|
398
|
+
if (!ctx || (typeof ctx !== 'object' && typeof ctx !== 'function')) return undefined;
|
|
399
|
+
const runjs = (ctx as { runjs?: unknown }).runjs;
|
|
400
|
+
if (typeof runjs !== 'function') return undefined;
|
|
401
|
+
return (ctx as { runjs: (code: string, variables?: Record<string, any>, options?: any) => Promise<any> }).runjs(
|
|
402
|
+
code,
|
|
403
|
+
createSafeRunJSGlobals(extraGlobals),
|
|
404
|
+
options,
|
|
405
|
+
);
|
|
406
|
+
}
|
|
@@ -277,3 +277,82 @@ export async function shouldHideStepInSettings<TModel extends FlowModel = FlowMo
|
|
|
277
277
|
|
|
278
278
|
return !!hideInSettings;
|
|
279
279
|
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* 解析步骤在设置菜单中的禁用状态与提示文案。
|
|
283
|
+
* - 支持 StepDefinition.disabledInSettings 与 ActionDefinition.disabledInSettings(step 优先)。
|
|
284
|
+
* - 支持 StepDefinition.disabledReasonInSettings 与 ActionDefinition.disabledReasonInSettings(step 优先)。
|
|
285
|
+
* - 以上属性均支持静态值与函数(接收 FlowRuntimeContext)。
|
|
286
|
+
*/
|
|
287
|
+
export async function resolveStepDisabledInSettings<TModel extends FlowModel = FlowModel>(
|
|
288
|
+
model: TModel,
|
|
289
|
+
flow: any,
|
|
290
|
+
step: StepDefinition,
|
|
291
|
+
): Promise<{ disabled: boolean; reason?: string }> {
|
|
292
|
+
if (!step) return { disabled: false };
|
|
293
|
+
|
|
294
|
+
let disabledInSettings = step.disabledInSettings;
|
|
295
|
+
let disabledReasonInSettings = step.disabledReasonInSettings;
|
|
296
|
+
|
|
297
|
+
if ((typeof disabledInSettings === 'undefined' || typeof disabledReasonInSettings === 'undefined') && step.use) {
|
|
298
|
+
try {
|
|
299
|
+
const action = model.getAction?.(step.use);
|
|
300
|
+
if (typeof disabledInSettings === 'undefined') {
|
|
301
|
+
disabledInSettings = action?.disabledInSettings;
|
|
302
|
+
}
|
|
303
|
+
if (typeof disabledReasonInSettings === 'undefined') {
|
|
304
|
+
disabledReasonInSettings = action?.disabledReasonInSettings;
|
|
305
|
+
}
|
|
306
|
+
} catch (error) {
|
|
307
|
+
console.warn(`Failed to get action ${step.use}:`, error);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
let ctx: FlowRuntimeContext<TModel> | null = null;
|
|
312
|
+
const getContext = () => {
|
|
313
|
+
if (ctx) return ctx;
|
|
314
|
+
ctx = new FlowRuntimeContext(model, flow.key, 'settings');
|
|
315
|
+
setupRuntimeContextSteps(ctx, flow.steps, model, flow.key);
|
|
316
|
+
ctx.defineProperty('currentStep', { value: step });
|
|
317
|
+
return ctx;
|
|
318
|
+
};
|
|
319
|
+
|
|
320
|
+
let disabled = false;
|
|
321
|
+
if (typeof disabledInSettings === 'function') {
|
|
322
|
+
try {
|
|
323
|
+
disabled = !!(await disabledInSettings(getContext() as any));
|
|
324
|
+
} catch (error) {
|
|
325
|
+
console.warn(`Error evaluating disabledInSettings for step '${step.key || ''}' in flow '${flow.key}':`, error);
|
|
326
|
+
return { disabled: false };
|
|
327
|
+
}
|
|
328
|
+
} else {
|
|
329
|
+
disabled = !!disabledInSettings;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
if (!disabled) {
|
|
333
|
+
return { disabled: false };
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
let reason: string | undefined;
|
|
337
|
+
if (typeof disabledReasonInSettings === 'function') {
|
|
338
|
+
try {
|
|
339
|
+
const resolved = await disabledReasonInSettings(getContext() as any);
|
|
340
|
+
if (typeof resolved !== 'undefined' && resolved !== null && resolved !== '') {
|
|
341
|
+
reason = String(resolved);
|
|
342
|
+
}
|
|
343
|
+
} catch (error) {
|
|
344
|
+
console.warn(
|
|
345
|
+
`Error evaluating disabledReasonInSettings for step '${step.key || ''}' in flow '${flow.key}':`,
|
|
346
|
+
error,
|
|
347
|
+
);
|
|
348
|
+
}
|
|
349
|
+
} else if (
|
|
350
|
+
typeof disabledReasonInSettings !== 'undefined' &&
|
|
351
|
+
disabledReasonInSettings !== null &&
|
|
352
|
+
disabledReasonInSettings !== ''
|
|
353
|
+
) {
|
|
354
|
+
reason = String(disabledReasonInSettings);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
return { disabled: true, reason };
|
|
358
|
+
}
|
|
@@ -13,7 +13,7 @@ import { render, act, waitFor, screen } from '@testing-library/react';
|
|
|
13
13
|
import { FlowEngine } from '../../flowEngine';
|
|
14
14
|
import { FlowEngineProvider } from '../../provider';
|
|
15
15
|
import { FlowViewer } from '../FlowView';
|
|
16
|
-
import { usePage } from '../usePage';
|
|
16
|
+
import { usePage, GLOBAL_EMBED_CONTAINER_ID } from '../usePage';
|
|
17
17
|
import { App, ConfigProvider } from 'antd';
|
|
18
18
|
|
|
19
19
|
describe('FlowViewer zIndex with usePage', () => {
|
|
@@ -130,4 +130,57 @@ describe('FlowViewer zIndex with usePage', () => {
|
|
|
130
130
|
|
|
131
131
|
unmount();
|
|
132
132
|
});
|
|
133
|
+
|
|
134
|
+
it('replaces previous embed view when using global #nocobase-embed-container target', async () => {
|
|
135
|
+
let api: { open: (config: any, flowContext: any) => any } | undefined;
|
|
136
|
+
|
|
137
|
+
function TestApp({ onReady }: { onReady: (page: any) => void }) {
|
|
138
|
+
const [page, pageHolder] = usePage() as [{ open: (config: any, flowContext: any) => any }, React.ReactNode];
|
|
139
|
+
|
|
140
|
+
React.useEffect(() => {
|
|
141
|
+
onReady(page);
|
|
142
|
+
}, [page, onReady]);
|
|
143
|
+
|
|
144
|
+
return <>{pageHolder}</>;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const Wrapper: React.FC<{ onReady: (page: any) => void }> = ({ onReady }) => (
|
|
148
|
+
<ConfigProvider>
|
|
149
|
+
<App>
|
|
150
|
+
<FlowEngineProvider engine={engine}>
|
|
151
|
+
<TestApp onReady={onReady} />
|
|
152
|
+
</FlowEngineProvider>
|
|
153
|
+
</App>
|
|
154
|
+
</ConfigProvider>
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
const target = document.createElement('div');
|
|
158
|
+
target.id = GLOBAL_EMBED_CONTAINER_ID;
|
|
159
|
+
document.body.appendChild(target);
|
|
160
|
+
|
|
161
|
+
const { unmount } = render(
|
|
162
|
+
<Wrapper
|
|
163
|
+
onReady={(page) => {
|
|
164
|
+
api = page;
|
|
165
|
+
}}
|
|
166
|
+
/>,
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
await waitFor(() => expect(api).toBeDefined());
|
|
170
|
+
|
|
171
|
+
await act(async () => {
|
|
172
|
+
api!.open({ target, content: <div data-testid="page1">Page 1</div> }, engine.context);
|
|
173
|
+
});
|
|
174
|
+
await waitFor(() => expect(screen.getByTestId('page1')).toBeInTheDocument());
|
|
175
|
+
|
|
176
|
+
// Opening page2 into the global embed container should destroy page1 (replace behavior).
|
|
177
|
+
await act(async () => {
|
|
178
|
+
api!.open({ target, content: <div data-testid="page2">Page 2</div> }, engine.context);
|
|
179
|
+
});
|
|
180
|
+
await waitFor(() => expect(screen.getByTestId('page2')).toBeInTheDocument());
|
|
181
|
+
expect(screen.queryByTestId('page1')).not.toBeInTheDocument();
|
|
182
|
+
|
|
183
|
+
unmount();
|
|
184
|
+
document.body.removeChild(target);
|
|
185
|
+
});
|
|
133
186
|
});
|