@nocobase/client-v2 2.1.0-alpha.45 → 2.1.0-alpha.46
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/authRedirect.d.ts +12 -0
- package/es/components/form/TypedVariableInput.d.ts +75 -0
- package/es/components/form/index.d.ts +1 -0
- package/es/flow/admin-shell/admin-layout/AdminLayoutMenuModels.d.ts +1 -0
- package/es/flow/components/ConditionBuilder.d.ts +1 -0
- package/es/flow/components/FieldAssignRulesEditor.d.ts +3 -1
- package/es/flow/components/FieldAssignValueInput.d.ts +1 -0
- package/es/flow/components/fieldAssignOptions.d.ts +10 -0
- package/es/flow/components/filter/LinkageFilterItem.d.ts +1 -0
- package/es/flow/components/filter/VariableFilterItem.d.ts +1 -0
- package/es/flow/components/filter/metaTreeAssociationDepth.d.ts +13 -0
- package/es/flow/models/base/AssociationFieldGroupModel.d.ts +2 -33
- package/es/flow/models/blocks/assign-form/assignFieldValuesFlow.d.ts +8 -0
- package/es/flow/models/blocks/table/TableColumnModel.d.ts +2 -0
- package/es/index.mjs +103 -103
- package/lib/index.js +105 -105
- package/package.json +7 -7
- package/src/__tests__/nocobase-buildin-plugin-auth.test.tsx +5 -0
- package/src/authRedirect.ts +43 -0
- package/src/components/README.md +48 -0
- package/src/components/README.zh-CN.md +48 -0
- package/src/components/form/TypedVariableInput.tsx +441 -0
- package/src/components/form/__tests__/TypedVariableInput.test.tsx +152 -0
- package/src/components/form/index.tsx +1 -0
- package/src/flow/__tests__/FlowRoute.test.tsx +8 -1
- package/src/flow/actions/__tests__/linkageRules.hiddenSync.restore.test.ts +100 -0
- package/src/flow/actions/__tests__/linkageRulesRefresh.test.ts +23 -1
- package/src/flow/actions/dataScope.tsx +8 -1
- package/src/flow/actions/linkageRules.tsx +8 -5
- package/src/flow/actions/linkageRulesRefresh.tsx +2 -3
- package/src/flow/actions/setTargetDataScope.tsx +1 -1
- package/src/flow/admin-shell/admin-layout/AdminLayoutMenuModels.tsx +15 -1
- package/src/flow/admin-shell/admin-layout/__tests__/AdminLayoutMenuModels.test.ts +75 -1
- package/src/flow/admin-shell/admin-layout/resolveAdminRouteRuntimeTarget.test.ts +11 -0
- package/src/flow/admin-shell/admin-layout/resolveAdminRouteRuntimeTarget.ts +2 -4
- package/src/flow/common/Markdown/Edit.tsx +32 -4
- package/src/flow/components/ConditionBuilder.tsx +12 -2
- package/src/flow/components/FieldAssignRulesEditor.tsx +37 -46
- package/src/flow/components/FieldAssignValueInput.tsx +8 -4
- package/src/flow/components/__tests__/FieldAssignRulesEditor.test.tsx +112 -1
- package/src/flow/components/__tests__/fieldAssignOptions.test.ts +71 -1
- package/src/flow/components/code-editor/__tests__/runjsCompletions.test.ts +272 -27
- package/src/flow/components/code-editor/runjsCompletions.ts +490 -9
- package/src/flow/components/fieldAssignOptions.ts +23 -6
- package/src/flow/components/filter/LinkageFilterItem.tsx +22 -9
- package/src/flow/components/filter/VariableFilterItem.tsx +27 -9
- package/src/flow/components/filter/__tests__/fieldsToOptions.test.ts +48 -0
- package/src/flow/components/filter/__tests__/metaTreeAssociationDepth.test.ts +60 -0
- package/src/flow/components/filter/fieldsToOptions.ts +9 -4
- package/src/flow/components/filter/metaTreeAssociationDepth.ts +69 -0
- package/src/flow/models/actions/FilterActionModel.tsx +15 -3
- package/src/flow/models/actions/__tests__/AssignFormRefill.test.ts +90 -0
- package/src/flow/models/base/AssociationFieldGroupModel.tsx +20 -3
- package/src/flow/models/base/GridModel.tsx +1 -1
- package/src/flow/models/base/PageModel/ChildPageModel.tsx +5 -1
- package/src/flow/models/base/PageModel/__tests__/ChildPageModel.test.ts +12 -0
- package/src/flow/models/base/__tests__/AssociationFieldGroupModel.test.ts +86 -0
- package/src/flow/models/base/__tests__/GridModel.render.test.tsx +53 -0
- package/src/flow/models/blocks/assign-form/AssignFormGridModel.tsx +12 -3
- package/src/flow/models/blocks/assign-form/AssignFormItemModel.tsx +14 -4
- package/src/flow/models/blocks/assign-form/__tests__/assignFieldValuesFlow.editor.test.tsx +216 -0
- package/src/flow/models/blocks/assign-form/assignFieldValuesFlow.tsx +142 -40
- package/src/flow/models/blocks/filter-form/FilterFormItemModel.tsx +1 -1
- package/src/flow/models/blocks/filter-form/__tests__/FilterFormItemModel.defineChildren.test.ts +6 -1
- package/src/flow/models/blocks/filter-manager/flow-actions/connectFields.tsx +48 -6
- package/src/flow/models/blocks/form/FormAssociationItemModel.tsx +9 -0
- package/src/flow/models/blocks/form/FormItemModel.tsx +9 -0
- package/src/flow/models/blocks/form/__tests__/FormBlockModel.test.tsx +17 -0
- package/src/flow/models/blocks/form/__tests__/FormItemModel.defineChildren.test.ts +27 -2
- package/src/flow/models/blocks/form/value-runtime/__tests__/runtime.test.ts +29 -0
- package/src/flow/models/blocks/form/value-runtime/runtime.ts +5 -1
- package/src/flow/models/blocks/table/TableColumnModel.tsx +15 -5
- package/src/flow/models/blocks/table/TableCustomColumnModel.tsx +2 -2
- package/src/flow/models/blocks/table/__tests__/TableColumnModel.test.tsx +18 -0
- package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/SubTableColumnModel.tsx +6 -7
- package/src/flow/models/fields/mobile-components/MobileLazySelect.tsx +5 -3
- package/src/flow/models/fields/mobile-components/__tests__/MobileSelect.test.tsx +70 -0
|
@@ -9,7 +9,12 @@
|
|
|
9
9
|
|
|
10
10
|
import { Completion } from '@codemirror/autocomplete';
|
|
11
11
|
import { EditorView } from '@codemirror/view';
|
|
12
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
getRunJSDocFor,
|
|
14
|
+
setupRunJSContexts,
|
|
15
|
+
listSnippetsForContext,
|
|
16
|
+
type RunJSDocCompletionDoc,
|
|
17
|
+
} from '@nocobase/flow-engine';
|
|
13
18
|
import { formatDocInfo } from './formatDocInfo';
|
|
14
19
|
|
|
15
20
|
export type SnippetEntry = {
|
|
@@ -23,6 +28,483 @@ export type SnippetEntry = {
|
|
|
23
28
|
scenes?: string[];
|
|
24
29
|
};
|
|
25
30
|
|
|
31
|
+
type StaticCompletionEntry = {
|
|
32
|
+
label: string;
|
|
33
|
+
type: Completion['type'];
|
|
34
|
+
detail?: string;
|
|
35
|
+
info?: string;
|
|
36
|
+
insertText?: string;
|
|
37
|
+
boost?: number;
|
|
38
|
+
requires?: Array<'element'>;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const NON_ELEMENT_COMPLETION_SCENES = new Set(['eventFlow', 'formValue', 'linkage']);
|
|
42
|
+
|
|
43
|
+
const normalizeScenes = (scene?: string | string[]): string[] =>
|
|
44
|
+
(Array.isArray(scene) ? scene : scene ? [scene] : [])
|
|
45
|
+
.map((sceneName) => (typeof sceneName === 'string' ? sceneName.trim() : ''))
|
|
46
|
+
.filter(Boolean);
|
|
47
|
+
|
|
48
|
+
const hasRunJSElementContext = (doc: any, apiInfos: any, requestedScenes: string[]): boolean => {
|
|
49
|
+
if (requestedScenes.some((sceneName) => NON_ELEMENT_COMPLETION_SCENES.has(sceneName))) return false;
|
|
50
|
+
return !!((doc as any)?.properties?.element || (apiInfos as any)?.element);
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const usesCtxElement = (text?: string): boolean => typeof text === 'string' && /\bctx\.element\b/.test(text);
|
|
54
|
+
|
|
55
|
+
const satisfiesCompletionRequirements = (
|
|
56
|
+
completionSpec: Pick<RunJSDocCompletionDoc, 'requires'> | undefined,
|
|
57
|
+
capabilities: { element: boolean },
|
|
58
|
+
): boolean => {
|
|
59
|
+
if (!completionSpec?.requires?.length) return true;
|
|
60
|
+
return completionSpec.requires.every((requirement) => requirement !== 'element' || capabilities.element);
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const createApply = (insertText?: string) => (view: EditorView, _completion: Completion, from: number, to: number) => {
|
|
64
|
+
if (!insertText) return;
|
|
65
|
+
view.dispatch({
|
|
66
|
+
changes: { from, to, insert: insertText },
|
|
67
|
+
selection: { anchor: from + insertText.length },
|
|
68
|
+
scrollIntoView: true,
|
|
69
|
+
});
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const staticEntry = ({ label, type, detail, info, insertText, boost = 85 }: StaticCompletionEntry): Completion =>
|
|
73
|
+
({
|
|
74
|
+
label,
|
|
75
|
+
type,
|
|
76
|
+
detail,
|
|
77
|
+
info,
|
|
78
|
+
boost,
|
|
79
|
+
apply: insertText ? createApply(insertText) : undefined,
|
|
80
|
+
}) as Completion;
|
|
81
|
+
|
|
82
|
+
const SAFE_GLOBAL_COMPLETIONS: StaticCompletionEntry[] = [
|
|
83
|
+
{
|
|
84
|
+
label: 'window.open()',
|
|
85
|
+
type: 'function',
|
|
86
|
+
detail: '(url: string, target?: string, features?: string) => Window | null',
|
|
87
|
+
info: 'Open a safe http/https/about:blank URL in a new tab.',
|
|
88
|
+
insertText: "window.open('https://example.com')",
|
|
89
|
+
boost: 105,
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
label: 'window.addEventListener()',
|
|
93
|
+
type: 'function',
|
|
94
|
+
detail: '(type: string, listener: EventListener) => void',
|
|
95
|
+
info: 'Attach a listener to the safe window proxy.',
|
|
96
|
+
insertText: "window.addEventListener('resize', () => {})",
|
|
97
|
+
boost: 105,
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
label: 'window.setTimeout()',
|
|
101
|
+
type: 'function',
|
|
102
|
+
detail: '(handler: Function, timeout?: number) => number',
|
|
103
|
+
insertText: 'window.setTimeout(() => {}, 0)',
|
|
104
|
+
boost: 100,
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
label: 'window.clearTimeout()',
|
|
108
|
+
type: 'function',
|
|
109
|
+
detail: '(id?: number) => void',
|
|
110
|
+
insertText: 'window.clearTimeout(timer)',
|
|
111
|
+
boost: 100,
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
label: 'window.setInterval()',
|
|
115
|
+
type: 'function',
|
|
116
|
+
detail: '(handler: Function, timeout?: number) => number',
|
|
117
|
+
insertText: 'window.setInterval(() => {}, 1000)',
|
|
118
|
+
boost: 100,
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
label: 'window.clearInterval()',
|
|
122
|
+
type: 'function',
|
|
123
|
+
detail: '(id?: number) => void',
|
|
124
|
+
insertText: 'window.clearInterval(timer)',
|
|
125
|
+
boost: 100,
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
label: 'window.FormData',
|
|
129
|
+
type: 'class',
|
|
130
|
+
detail: 'FormData constructor',
|
|
131
|
+
insertText: 'new window.FormData()',
|
|
132
|
+
boost: 95,
|
|
133
|
+
},
|
|
134
|
+
{ label: 'window.Blob', type: 'class', detail: 'Blob constructor', insertText: 'window.Blob', boost: 95 },
|
|
135
|
+
{ label: 'window.URL', type: 'class', detail: 'URL constructor', insertText: 'window.URL', boost: 95 },
|
|
136
|
+
{ label: 'window.location.origin', type: 'property', detail: 'string', boost: 100 },
|
|
137
|
+
{ label: 'window.location.protocol', type: 'property', detail: 'string', boost: 100 },
|
|
138
|
+
{ label: 'window.location.host', type: 'property', detail: 'string', boost: 100 },
|
|
139
|
+
{ label: 'window.location.hostname', type: 'property', detail: 'string', boost: 100 },
|
|
140
|
+
{ label: 'window.location.port', type: 'property', detail: 'string', boost: 100 },
|
|
141
|
+
{ label: 'window.location.pathname', type: 'property', detail: 'string', boost: 100 },
|
|
142
|
+
{
|
|
143
|
+
label: 'window.location.assign()',
|
|
144
|
+
type: 'function',
|
|
145
|
+
detail: '(url: string) => void',
|
|
146
|
+
insertText: "window.location.assign('/path')",
|
|
147
|
+
boost: 105,
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
label: 'window.location.replace()',
|
|
151
|
+
type: 'function',
|
|
152
|
+
detail: '(url: string) => void',
|
|
153
|
+
insertText: "window.location.replace('/path')",
|
|
154
|
+
boost: 105,
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
label: 'window.location.reload()',
|
|
158
|
+
type: 'function',
|
|
159
|
+
detail: '() => void',
|
|
160
|
+
insertText: 'window.location.reload()',
|
|
161
|
+
boost: 110,
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
label: 'window.location.href =',
|
|
165
|
+
type: 'snippet',
|
|
166
|
+
detail: 'safe assignment only',
|
|
167
|
+
info: 'RunJS allows assigning window.location.href, but reading href is blocked.',
|
|
168
|
+
insertText: "window.location.href = '/path'",
|
|
169
|
+
boost: 95,
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
label: 'document.createElement()',
|
|
173
|
+
type: 'function',
|
|
174
|
+
detail: '(tagName: string) => HTMLElement',
|
|
175
|
+
insertText: "document.createElement('div')",
|
|
176
|
+
boost: 105,
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
label: 'document.querySelector()',
|
|
180
|
+
type: 'function',
|
|
181
|
+
detail: '(selectors: string) => Element | null',
|
|
182
|
+
insertText: "document.querySelector('.selector')",
|
|
183
|
+
boost: 105,
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
label: 'document.querySelectorAll()',
|
|
187
|
+
type: 'function',
|
|
188
|
+
detail: '(selectors: string) => NodeListOf<Element>',
|
|
189
|
+
insertText: "document.querySelectorAll('.selector')",
|
|
190
|
+
boost: 105,
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
label: 'navigator.clipboard.writeText()',
|
|
194
|
+
type: 'function',
|
|
195
|
+
detail: '(text: string) => Promise<void>',
|
|
196
|
+
insertText: "await navigator.clipboard.writeText('text')",
|
|
197
|
+
boost: 105,
|
|
198
|
+
},
|
|
199
|
+
{ label: 'navigator.onLine', type: 'property', detail: 'boolean', boost: 100 },
|
|
200
|
+
{ label: 'navigator.language', type: 'property', detail: 'string', boost: 100 },
|
|
201
|
+
{ label: 'navigator.languages', type: 'property', detail: 'string[]', boost: 100 },
|
|
202
|
+
{
|
|
203
|
+
label: 'console.log()',
|
|
204
|
+
type: 'function',
|
|
205
|
+
detail: '(...args: any[]) => void',
|
|
206
|
+
insertText: "console.log('message')",
|
|
207
|
+
boost: 95,
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
label: 'console.info()',
|
|
211
|
+
type: 'function',
|
|
212
|
+
detail: '(...args: any[]) => void',
|
|
213
|
+
insertText: "console.info('message')",
|
|
214
|
+
boost: 95,
|
|
215
|
+
},
|
|
216
|
+
{
|
|
217
|
+
label: 'console.warn()',
|
|
218
|
+
type: 'function',
|
|
219
|
+
detail: '(...args: any[]) => void',
|
|
220
|
+
insertText: "console.warn('message')",
|
|
221
|
+
boost: 95,
|
|
222
|
+
},
|
|
223
|
+
{
|
|
224
|
+
label: 'console.error()',
|
|
225
|
+
type: 'function',
|
|
226
|
+
detail: '(...args: any[]) => void',
|
|
227
|
+
insertText: "console.error('message')",
|
|
228
|
+
boost: 95,
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
label: 'setTimeout()',
|
|
232
|
+
type: 'function',
|
|
233
|
+
detail: '(handler: Function, timeout?: number) => number',
|
|
234
|
+
insertText: 'setTimeout(() => {}, 0)',
|
|
235
|
+
boost: 95,
|
|
236
|
+
},
|
|
237
|
+
{
|
|
238
|
+
label: 'clearTimeout()',
|
|
239
|
+
type: 'function',
|
|
240
|
+
detail: '(id?: number) => void',
|
|
241
|
+
insertText: 'clearTimeout(timer)',
|
|
242
|
+
boost: 95,
|
|
243
|
+
},
|
|
244
|
+
{
|
|
245
|
+
label: 'setInterval()',
|
|
246
|
+
type: 'function',
|
|
247
|
+
detail: '(handler: Function, timeout?: number) => number',
|
|
248
|
+
insertText: 'setInterval(() => {}, 1000)',
|
|
249
|
+
boost: 95,
|
|
250
|
+
},
|
|
251
|
+
{
|
|
252
|
+
label: 'clearInterval()',
|
|
253
|
+
type: 'function',
|
|
254
|
+
detail: '(id?: number) => void',
|
|
255
|
+
insertText: 'clearInterval(timer)',
|
|
256
|
+
boost: 95,
|
|
257
|
+
},
|
|
258
|
+
{ label: 'Blob', type: 'class', detail: 'Blob constructor', insertText: 'Blob', boost: 90 },
|
|
259
|
+
{ label: 'URL', type: 'class', detail: 'URL constructor', insertText: 'URL', boost: 90 },
|
|
260
|
+
];
|
|
261
|
+
|
|
262
|
+
const RUNJS_RUNTIME_COMPLETIONS: StaticCompletionEntry[] = [
|
|
263
|
+
{
|
|
264
|
+
label: 'ctx.runjs()',
|
|
265
|
+
type: 'function',
|
|
266
|
+
detail: '(code: string, variables?: object, options?: object) => Promise<any>',
|
|
267
|
+
insertText: "await ctx.runjs('return 1')",
|
|
268
|
+
boost: 105,
|
|
269
|
+
},
|
|
270
|
+
{
|
|
271
|
+
label: 'ctx.loadCSS()',
|
|
272
|
+
type: 'function',
|
|
273
|
+
detail: '(href: string) => Promise<void>',
|
|
274
|
+
insertText: "await ctx.loadCSS('https://example.com/style.css')",
|
|
275
|
+
boost: 105,
|
|
276
|
+
},
|
|
277
|
+
{
|
|
278
|
+
label: 'ctx.previewRunJS()',
|
|
279
|
+
type: 'function',
|
|
280
|
+
detail: '(code: string, version?: string) => Promise<PreviewRunJSResult>',
|
|
281
|
+
insertText: "await ctx.previewRunJS('console.log(1)', 'v2')",
|
|
282
|
+
boost: 105,
|
|
283
|
+
},
|
|
284
|
+
{
|
|
285
|
+
label: 'ctx.requireAsync()',
|
|
286
|
+
type: 'function',
|
|
287
|
+
detail: '(url: string) => Promise<any>',
|
|
288
|
+
insertText: "const lib = await ctx.requireAsync('https://cdn.example.com/lib.umd.js')",
|
|
289
|
+
boost: 100,
|
|
290
|
+
},
|
|
291
|
+
{
|
|
292
|
+
label: 'ctx.importAsync()',
|
|
293
|
+
type: 'function',
|
|
294
|
+
detail: '(url: string) => Promise<any>',
|
|
295
|
+
insertText: "const mod = await ctx.importAsync('lodash-es')",
|
|
296
|
+
boost: 100,
|
|
297
|
+
},
|
|
298
|
+
{
|
|
299
|
+
label: 'ctx.t()',
|
|
300
|
+
type: 'function',
|
|
301
|
+
detail: '(key: string, options?: object) => string',
|
|
302
|
+
insertText: "ctx.t('Hello')",
|
|
303
|
+
boost: 100,
|
|
304
|
+
},
|
|
305
|
+
{
|
|
306
|
+
label: 'ctx.resolveJsonTemplate()',
|
|
307
|
+
type: 'function',
|
|
308
|
+
detail: '(template: any) => Promise<any>',
|
|
309
|
+
insertText: "await ctx.resolveJsonTemplate({ value: '{{ctx.record.id}}' })",
|
|
310
|
+
boost: 100,
|
|
311
|
+
},
|
|
312
|
+
{
|
|
313
|
+
label: 'ctx.sql.run()',
|
|
314
|
+
type: 'function',
|
|
315
|
+
detail: '(sql: string, options?: SQLRunOptions) => Promise<any>',
|
|
316
|
+
insertText: "await ctx.sql.run('SELECT 1', { type: 'selectRows' })",
|
|
317
|
+
boost: 100,
|
|
318
|
+
},
|
|
319
|
+
{
|
|
320
|
+
label: 'ctx.sql.save()',
|
|
321
|
+
type: 'function',
|
|
322
|
+
detail: '(data: { uid: string; sql: string; dataSourceKey?: string }) => Promise<void>',
|
|
323
|
+
insertText: "await ctx.sql.save({ uid: 'sql-uid', sql: 'SELECT 1' })",
|
|
324
|
+
boost: 95,
|
|
325
|
+
},
|
|
326
|
+
{
|
|
327
|
+
label: 'ctx.sql.runById()',
|
|
328
|
+
type: 'function',
|
|
329
|
+
detail: '(uid: string, options?: SQLRunOptions) => Promise<any>',
|
|
330
|
+
insertText: "await ctx.sql.runById('sql-uid', { type: 'selectRows' })",
|
|
331
|
+
boost: 100,
|
|
332
|
+
},
|
|
333
|
+
{
|
|
334
|
+
label: 'ctx.sql.destroy()',
|
|
335
|
+
type: 'function',
|
|
336
|
+
detail: '(uid: string) => Promise<void>',
|
|
337
|
+
insertText: "await ctx.sql.destroy('sql-uid')",
|
|
338
|
+
boost: 95,
|
|
339
|
+
},
|
|
340
|
+
{
|
|
341
|
+
label: 'ctx.logger.info()',
|
|
342
|
+
type: 'function',
|
|
343
|
+
detail: 'Pino logger info',
|
|
344
|
+
insertText: "ctx.logger.info({ foo: 1 }, 'message')",
|
|
345
|
+
boost: 95,
|
|
346
|
+
},
|
|
347
|
+
{
|
|
348
|
+
label: 'ctx.logger.warn()',
|
|
349
|
+
type: 'function',
|
|
350
|
+
detail: 'Pino logger warn',
|
|
351
|
+
insertText: "ctx.logger.warn({ foo: 1 }, 'message')",
|
|
352
|
+
boost: 95,
|
|
353
|
+
},
|
|
354
|
+
{
|
|
355
|
+
label: 'ctx.logger.error()',
|
|
356
|
+
type: 'function',
|
|
357
|
+
detail: 'Pino logger error',
|
|
358
|
+
insertText: "ctx.logger.error({ err }, 'message')",
|
|
359
|
+
boost: 95,
|
|
360
|
+
},
|
|
361
|
+
{
|
|
362
|
+
label: 'ctx.logger.debug()',
|
|
363
|
+
type: 'function',
|
|
364
|
+
detail: 'Pino logger debug',
|
|
365
|
+
insertText: "ctx.logger.debug({ foo: 1 }, 'message')",
|
|
366
|
+
boost: 95,
|
|
367
|
+
},
|
|
368
|
+
{
|
|
369
|
+
label: 'ctx.logger.child()',
|
|
370
|
+
type: 'function',
|
|
371
|
+
detail: '(bindings: object) => Logger',
|
|
372
|
+
insertText: "ctx.logger.child({ module: 'runjs' })",
|
|
373
|
+
boost: 90,
|
|
374
|
+
},
|
|
375
|
+
{
|
|
376
|
+
label: 'ctx.libs.React.createElement()',
|
|
377
|
+
type: 'function',
|
|
378
|
+
detail: 'React.createElement',
|
|
379
|
+
insertText: "ctx.libs.React.createElement('div', null, 'Hello')",
|
|
380
|
+
boost: 90,
|
|
381
|
+
},
|
|
382
|
+
{
|
|
383
|
+
label: 'ctx.libs.React.useState()',
|
|
384
|
+
type: 'function',
|
|
385
|
+
detail: 'React.useState',
|
|
386
|
+
insertText: 'ctx.libs.React.useState(initialValue)',
|
|
387
|
+
boost: 90,
|
|
388
|
+
},
|
|
389
|
+
{
|
|
390
|
+
label: 'ctx.libs.React.useEffect()',
|
|
391
|
+
type: 'function',
|
|
392
|
+
detail: 'React.useEffect',
|
|
393
|
+
insertText: 'ctx.libs.React.useEffect(() => {}, [])',
|
|
394
|
+
boost: 90,
|
|
395
|
+
},
|
|
396
|
+
{
|
|
397
|
+
label: 'ctx.libs.React.useMemo()',
|
|
398
|
+
type: 'function',
|
|
399
|
+
detail: 'React.useMemo',
|
|
400
|
+
insertText: 'ctx.libs.React.useMemo(() => value, [])',
|
|
401
|
+
boost: 85,
|
|
402
|
+
},
|
|
403
|
+
{
|
|
404
|
+
label: 'ctx.libs.React.useCallback()',
|
|
405
|
+
type: 'function',
|
|
406
|
+
detail: 'React.useCallback',
|
|
407
|
+
insertText: 'ctx.libs.React.useCallback(() => {}, [])',
|
|
408
|
+
boost: 85,
|
|
409
|
+
},
|
|
410
|
+
{
|
|
411
|
+
label: 'ctx.libs.ReactDOM.createRoot()',
|
|
412
|
+
type: 'function',
|
|
413
|
+
detail: 'ReactDOM.createRoot',
|
|
414
|
+
insertText: 'ctx.libs.ReactDOM.createRoot(ctx.element.__el)',
|
|
415
|
+
boost: 90,
|
|
416
|
+
requires: ['element'],
|
|
417
|
+
},
|
|
418
|
+
{ label: 'ctx.libs.antd.Button', type: 'class', detail: 'Ant Design Button', boost: 90 },
|
|
419
|
+
{ label: 'ctx.libs.antd.Table', type: 'class', detail: 'Ant Design Table', boost: 90 },
|
|
420
|
+
{ label: 'ctx.libs.antd.Form', type: 'class', detail: 'Ant Design Form', boost: 90 },
|
|
421
|
+
{ label: 'ctx.libs.antd.Input', type: 'class', detail: 'Ant Design Input', boost: 90 },
|
|
422
|
+
{ label: 'ctx.libs.antd.Select', type: 'class', detail: 'Ant Design Select', boost: 90 },
|
|
423
|
+
{ label: 'ctx.libs.antd.Modal', type: 'class', detail: 'Ant Design Modal', boost: 90 },
|
|
424
|
+
{ label: 'ctx.libs.dayjs()', type: 'function', detail: 'dayjs', insertText: 'ctx.libs.dayjs()', boost: 90 },
|
|
425
|
+
{
|
|
426
|
+
label: 'ctx.libs.lodash.get()',
|
|
427
|
+
type: 'function',
|
|
428
|
+
detail: 'lodash.get',
|
|
429
|
+
insertText: "ctx.libs.lodash.get(obj, 'a.b')",
|
|
430
|
+
boost: 90,
|
|
431
|
+
},
|
|
432
|
+
{
|
|
433
|
+
label: 'ctx.libs.lodash.set()',
|
|
434
|
+
type: 'function',
|
|
435
|
+
detail: 'lodash.set',
|
|
436
|
+
insertText: "ctx.libs.lodash.set(obj, 'a.b', value)",
|
|
437
|
+
boost: 85,
|
|
438
|
+
},
|
|
439
|
+
{
|
|
440
|
+
label: 'ctx.libs.lodash.debounce()',
|
|
441
|
+
type: 'function',
|
|
442
|
+
detail: 'lodash.debounce',
|
|
443
|
+
insertText: 'ctx.libs.lodash.debounce(() => {}, 300)',
|
|
444
|
+
boost: 85,
|
|
445
|
+
},
|
|
446
|
+
{
|
|
447
|
+
label: 'ctx.libs.lodash.cloneDeep()',
|
|
448
|
+
type: 'function',
|
|
449
|
+
detail: 'lodash.cloneDeep',
|
|
450
|
+
insertText: 'ctx.libs.lodash.cloneDeep(value)',
|
|
451
|
+
boost: 85,
|
|
452
|
+
},
|
|
453
|
+
{
|
|
454
|
+
label: 'ctx.libs.formula.SUM()',
|
|
455
|
+
type: 'function',
|
|
456
|
+
detail: 'Formula.js SUM',
|
|
457
|
+
insertText: 'ctx.libs.formula.SUM(1, 2, 3)',
|
|
458
|
+
boost: 85,
|
|
459
|
+
},
|
|
460
|
+
{
|
|
461
|
+
label: 'ctx.libs.formula.AVERAGE()',
|
|
462
|
+
type: 'function',
|
|
463
|
+
detail: 'Formula.js AVERAGE',
|
|
464
|
+
insertText: 'ctx.libs.formula.AVERAGE(1, 2, 3)',
|
|
465
|
+
boost: 85,
|
|
466
|
+
},
|
|
467
|
+
{
|
|
468
|
+
label: 'ctx.libs.math.evaluate()',
|
|
469
|
+
type: 'function',
|
|
470
|
+
detail: 'mathjs evaluate',
|
|
471
|
+
insertText: "ctx.libs.math.evaluate('2 + 3')",
|
|
472
|
+
boost: 85,
|
|
473
|
+
},
|
|
474
|
+
{
|
|
475
|
+
label: 'ctx.libs.math.round()',
|
|
476
|
+
type: 'function',
|
|
477
|
+
detail: 'mathjs round',
|
|
478
|
+
insertText: 'ctx.libs.math.round(value, 2)',
|
|
479
|
+
boost: 85,
|
|
480
|
+
},
|
|
481
|
+
{
|
|
482
|
+
label: 'ctx.React.createElement()',
|
|
483
|
+
type: 'function',
|
|
484
|
+
detail: 'React.createElement alias',
|
|
485
|
+
insertText: "ctx.React.createElement('div', null, 'Hello')",
|
|
486
|
+
boost: 80,
|
|
487
|
+
},
|
|
488
|
+
{
|
|
489
|
+
label: 'ctx.ReactDOM.createRoot()',
|
|
490
|
+
type: 'function',
|
|
491
|
+
detail: 'ReactDOM.createRoot alias',
|
|
492
|
+
insertText: 'ctx.ReactDOM.createRoot(ctx.element.__el)',
|
|
493
|
+
boost: 80,
|
|
494
|
+
requires: ['element'],
|
|
495
|
+
},
|
|
496
|
+
{ label: 'ctx.antd.Button', type: 'class', detail: 'Ant Design Button alias', boost: 80 },
|
|
497
|
+
{ label: 'ctx.antd.Modal', type: 'class', detail: 'Ant Design Modal alias', boost: 80 },
|
|
498
|
+
{ label: 'ctx.dayjs()', type: 'function', detail: 'dayjs alias', insertText: 'ctx.dayjs()', boost: 80 },
|
|
499
|
+
];
|
|
500
|
+
|
|
501
|
+
function buildStaticRuntimeCompletions(capabilities: { element: boolean }): Completion[] {
|
|
502
|
+
return [...SAFE_GLOBAL_COMPLETIONS, ...RUNJS_RUNTIME_COMPLETIONS]
|
|
503
|
+
.filter((entry) => satisfiesCompletionRequirements(entry, capabilities))
|
|
504
|
+
.filter((entry) => capabilities.element || !usesCtxElement(entry.insertText))
|
|
505
|
+
.map(staticEntry);
|
|
506
|
+
}
|
|
507
|
+
|
|
26
508
|
export async function buildRunJSCompletions(
|
|
27
509
|
hostCtx: any,
|
|
28
510
|
version = 'v1',
|
|
@@ -31,6 +513,7 @@ export async function buildRunJSCompletions(
|
|
|
31
513
|
completions: Completion[];
|
|
32
514
|
entries: SnippetEntry[];
|
|
33
515
|
}> {
|
|
516
|
+
const requestedScenes = normalizeScenes(scene);
|
|
34
517
|
const isFunctionLikeDocNode = (node: any, completionSpec: any): boolean => {
|
|
35
518
|
if (!node || typeof node !== 'object' || Array.isArray(node)) return false;
|
|
36
519
|
const type = typeof (node as any).type === 'string' ? String((node as any).type) : '';
|
|
@@ -56,7 +539,7 @@ export async function buildRunJSCompletions(
|
|
|
56
539
|
let apiInfos: any = null;
|
|
57
540
|
try {
|
|
58
541
|
if (hostCtx && typeof (hostCtx as any).getApiInfos === 'function') {
|
|
59
|
-
apiInfos = await (hostCtx as any).getApiInfos({ version });
|
|
542
|
+
apiInfos = await (hostCtx as any).getApiInfos({ version, includeCompletion: true });
|
|
60
543
|
}
|
|
61
544
|
} catch (_) {
|
|
62
545
|
apiInfos = null;
|
|
@@ -73,10 +556,12 @@ export async function buildRunJSCompletions(
|
|
|
73
556
|
};
|
|
74
557
|
const docApis = { ...((doc as any)?.properties || {}), ...normalizeMethodsRecord((doc as any)?.methods) };
|
|
75
558
|
const apisSource = { ...(apiInfos && typeof apiInfos === 'object' ? apiInfos : {}), ...(docApis || {}) };
|
|
76
|
-
const
|
|
559
|
+
const hasElementContext = hasRunJSElementContext(doc, apiInfos, requestedScenes);
|
|
560
|
+
const completions: Completion[] = buildStaticRuntimeCompletions({ element: hasElementContext });
|
|
77
561
|
const priorityRoots = new Set(['api', 'resource', 'viewer', 'record', 'formValues', 'popup']);
|
|
78
562
|
const hiddenDecisionCache = new Map<string, { hideSelf: boolean; hideSubpaths: string[] }>();
|
|
79
563
|
const hiddenPathPrefixes = new Set<string>();
|
|
564
|
+
if (!hasElementContext) hiddenPathPrefixes.add('ctx.element');
|
|
80
565
|
|
|
81
566
|
const isHiddenByPaths = (label: string) => {
|
|
82
567
|
if (!label || typeof label !== 'string') return false;
|
|
@@ -201,6 +686,8 @@ export async function buildRunJSCompletions(
|
|
|
201
686
|
const insertText =
|
|
202
687
|
completionSpec?.insertText ??
|
|
203
688
|
(root === 'popup' ? `await ctx.getVar('${ctxLabel}')` : isCallable ? `${ctxLabel}()` : undefined);
|
|
689
|
+
if (!satisfiesCompletionRequirements(completionSpec, { element: hasElementContext })) continue;
|
|
690
|
+
if (!hasElementContext && usesCtxElement(insertText)) continue;
|
|
204
691
|
const apply = insertText
|
|
205
692
|
? (view: EditorView, _completion: Completion, from: number, to: number) => {
|
|
206
693
|
view.dispatch({
|
|
@@ -235,12 +722,6 @@ export async function buildRunJSCompletions(
|
|
|
235
722
|
entries = [];
|
|
236
723
|
}
|
|
237
724
|
|
|
238
|
-
const requestedScenes = Array.isArray(scene)
|
|
239
|
-
? scene.filter((s): s is string => typeof s === 'string' && s.trim().length > 0)
|
|
240
|
-
: scene
|
|
241
|
-
? [scene]
|
|
242
|
-
: [];
|
|
243
|
-
|
|
244
725
|
const filteredEntries = requestedScenes.length
|
|
245
726
|
? entries.filter((entry) => {
|
|
246
727
|
if (entry.scenes?.length) {
|
|
@@ -76,10 +76,15 @@ function getCollectionFromFormBlockModel(model: any): CollectionLike | null {
|
|
|
76
76
|
return collection && typeof collection === 'object' ? (collection as CollectionLike) : null;
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
-
function
|
|
79
|
+
export function buildFieldAssignCascaderOptionsFromCollection(
|
|
80
80
|
collection: CollectionLike | null,
|
|
81
81
|
t: (key: string) => string,
|
|
82
|
+
options: {
|
|
83
|
+
associationDepth?: number;
|
|
84
|
+
maxAssociationFieldDepth?: number;
|
|
85
|
+
} = {},
|
|
82
86
|
): FieldAssignCascaderOption[] {
|
|
87
|
+
const { associationDepth = 0, maxAssociationFieldDepth = 2 } = options;
|
|
83
88
|
const fields = typeof collection?.getFields === 'function' ? collection.getFields() || [] : [];
|
|
84
89
|
const out: FieldAssignCascaderOption[] = [];
|
|
85
90
|
for (const rawField of fields) {
|
|
@@ -88,6 +93,7 @@ function buildRootOptionsFromCollection(
|
|
|
88
93
|
name?: unknown;
|
|
89
94
|
title?: unknown;
|
|
90
95
|
interface?: unknown;
|
|
96
|
+
target?: unknown;
|
|
91
97
|
isAssociationField?: () => boolean;
|
|
92
98
|
targetCollection?: any;
|
|
93
99
|
};
|
|
@@ -99,8 +105,11 @@ function buildRootOptionsFromCollection(
|
|
|
99
105
|
if (!name) continue;
|
|
100
106
|
const title = t(typeof f.title === 'string' ? f.title : name);
|
|
101
107
|
|
|
102
|
-
const isAssoc = !!f.isAssociationField?.();
|
|
108
|
+
const isAssoc = !!(f.isAssociationField?.() || f.target || f.targetCollection);
|
|
103
109
|
const hasTarget = !!f.targetCollection;
|
|
110
|
+
if (isAssoc && associationDepth >= maxAssociationFieldDepth) {
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
104
113
|
|
|
105
114
|
out.push({
|
|
106
115
|
label: title,
|
|
@@ -138,8 +147,9 @@ export function collectFieldAssignCascaderOptions(options: {
|
|
|
138
147
|
t: (key: string) => string;
|
|
139
148
|
/** 子表单模型递归深度(FormItemModel 层级);默认不限制(只受实际配置与循环引用约束) */
|
|
140
149
|
maxFormItemDepth?: number;
|
|
150
|
+
maxAssociationFieldDepth?: number;
|
|
141
151
|
}): FieldAssignCascaderOption[] {
|
|
142
|
-
const { formBlockModel, t, maxFormItemDepth = Number.POSITIVE_INFINITY } = options;
|
|
152
|
+
const { formBlockModel, t, maxFormItemDepth = Number.POSITIVE_INFINITY, maxAssociationFieldDepth = 2 } = options;
|
|
143
153
|
|
|
144
154
|
const rootItems = formBlockModel?.subModels?.grid?.subModels?.items || [];
|
|
145
155
|
const rootCollection = getCollectionFromFormBlockModel(formBlockModel);
|
|
@@ -167,9 +177,14 @@ export function collectFieldAssignCascaderOptions(options: {
|
|
|
167
177
|
|
|
168
178
|
const fieldModel = item?.subModels?.field;
|
|
169
179
|
const childItems = fieldModel?.subModels?.grid?.subModels?.items;
|
|
170
|
-
const cf: CollectionField | undefined =
|
|
171
|
-
|
|
180
|
+
const cf: (CollectionField & { target?: unknown; targetCollection?: unknown }) | undefined =
|
|
181
|
+
fieldModel?.context?.collectionField;
|
|
182
|
+
const isAssociation = !!(cf?.isAssociationField?.() || cf?.target || cf?.targetCollection);
|
|
172
183
|
const hasTargetCollection = !!cf?.targetCollection;
|
|
184
|
+
const associationDepth = isAssociation ? targetPath.split('.').filter(Boolean).length : 0;
|
|
185
|
+
if (isAssociation && associationDepth > maxAssociationFieldDepth) {
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
173
188
|
|
|
174
189
|
// 1) 子表单/子表单列表:递归展开已配置字段(支持无限深度)。
|
|
175
190
|
//
|
|
@@ -207,6 +222,8 @@ export function collectFieldAssignCascaderOptions(options: {
|
|
|
207
222
|
};
|
|
208
223
|
|
|
209
224
|
const configuredOptions = walkItems(rootItems, 1);
|
|
210
|
-
const allFieldOptions =
|
|
225
|
+
const allFieldOptions = buildFieldAssignCascaderOptionsFromCollection(rootCollection, t, {
|
|
226
|
+
maxAssociationFieldDepth,
|
|
227
|
+
});
|
|
211
228
|
return mergeRootOptions(configuredOptions, allFieldOptions);
|
|
212
229
|
}
|
|
@@ -30,6 +30,7 @@ import {
|
|
|
30
30
|
} from '../../internal/utils/enumOptionsUtils';
|
|
31
31
|
import { mergeItemMetaTreeForAssignValue } from '../FieldAssignValueInput';
|
|
32
32
|
import { resolveOperatorComponent } from '../../internal/utils/operatorSchemaHelper';
|
|
33
|
+
import { limitAssociationMetaTree } from './metaTreeAssociationDepth';
|
|
33
34
|
|
|
34
35
|
const { DateFilterDynamicComponent: DateFilterDynamicComponentLazy } = lazy(
|
|
35
36
|
() => import('../../models/blocks/filter-form/fields/date-time/components/DateFilterDynamicComponent'),
|
|
@@ -49,6 +50,14 @@ export interface LinkageFilterItemProps {
|
|
|
49
50
|
model: FlowModel;
|
|
50
51
|
/** 向变量树额外注入节点(置于根部) */
|
|
51
52
|
extraMetaTree?: MetaTreeNode[];
|
|
53
|
+
maxAssociationFieldDepth?: number;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function limitMetaTreeIfNeeded(nodes: MetaTreeNode[], maxAssociationFieldDepth?: number) {
|
|
57
|
+
if (typeof maxAssociationFieldDepth !== 'number') {
|
|
58
|
+
return nodes;
|
|
59
|
+
}
|
|
60
|
+
return limitAssociationMetaTree(nodes, { maxAssociationDepth: maxAssociationFieldDepth });
|
|
52
61
|
}
|
|
53
62
|
|
|
54
63
|
function createStaticInputRenderer(
|
|
@@ -221,7 +230,7 @@ export async function enhanceMetaTreeWithFilterableChildren(
|
|
|
221
230
|
* LinkageFilterItem:左/右均为可变量输入,适用于联动规则等“前端逻辑”场景
|
|
222
231
|
*/
|
|
223
232
|
export const LinkageFilterItem: React.FC<LinkageFilterItemProps> = observer((props) => {
|
|
224
|
-
const { value, model, extraMetaTree } = props;
|
|
233
|
+
const { value, model, extraMetaTree, maxAssociationFieldDepth } = props;
|
|
225
234
|
const ctx = useFlowViewContext();
|
|
226
235
|
const t = model.translate;
|
|
227
236
|
const { path: leftPath, operator: selectedOperator, value: rightOperandValue } = value || {};
|
|
@@ -386,13 +395,16 @@ export const LinkageFilterItem: React.FC<LinkageFilterItemProps> = observer((pro
|
|
|
386
395
|
return async () => {
|
|
387
396
|
const baseMetaTree = (model?.context.getPropertyMetaTree() || ctx.getPropertyMetaTree()) as MetaTreeNode[];
|
|
388
397
|
const mergedMetaTree = mergeExtraMetaTreeWithBase(baseMetaTree, extraMetaTree);
|
|
389
|
-
return
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
398
|
+
return limitMetaTreeIfNeeded(
|
|
399
|
+
[
|
|
400
|
+
{ title: t('Constant'), name: 'constant', type: 'string', paths: ['constant'] },
|
|
401
|
+
{ title: t('Null'), name: 'null', type: 'object', paths: ['null'] },
|
|
402
|
+
...mergedMetaTree,
|
|
403
|
+
],
|
|
404
|
+
maxAssociationFieldDepth,
|
|
405
|
+
);
|
|
394
406
|
};
|
|
395
|
-
}, [ctx, model, t, extraMetaTree]);
|
|
407
|
+
}, [ctx, model, t, extraMetaTree, maxAssociationFieldDepth]);
|
|
396
408
|
|
|
397
409
|
// 左侧变量树:默认整棵 ctx(不追加 Constant/Null),确保可获取字段 interface
|
|
398
410
|
const leftMetaTreeGetter = useCallback(async () => {
|
|
@@ -403,8 +415,9 @@ export const LinkageFilterItem: React.FC<LinkageFilterItemProps> = observer((pro
|
|
|
403
415
|
getFlowFieldInterfaceOptions(name, model.context.dataSourceManager, model.context.app?.dataSourceManager) as
|
|
404
416
|
| FieldInterfaceDef
|
|
405
417
|
| undefined;
|
|
406
|
-
|
|
407
|
-
|
|
418
|
+
const enhanced = await enhanceMetaTreeWithFilterableChildren(merged, getFieldInterface);
|
|
419
|
+
return limitMetaTreeIfNeeded(enhanced, maxAssociationFieldDepth);
|
|
420
|
+
}, [ctx, model, extraMetaTree, maxAssociationFieldDepth]);
|
|
408
421
|
|
|
409
422
|
// 右侧 converters:常量/空值特殊处理;变量沿用默认(表达式)
|
|
410
423
|
const rightSideConverters = useMemo<Converters>(() => {
|