@nocobase/flow-engine 2.1.0-beta.35 → 2.1.0-beta.36
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/components/subModel/LazyDropdown.js +17 -6
- package/lib/components/subModel/utils.js +7 -1
- package/lib/data-source/index.js +1 -1
- package/lib/utils/index.d.ts +1 -0
- package/lib/utils/index.js +3 -0
- package/lib/utils/randomId.d.ts +39 -0
- package/lib/utils/randomId.js +45 -0
- package/package.json +4 -4
- package/src/components/subModel/LazyDropdown.tsx +18 -7
- package/src/components/subModel/__tests__/AddSubModelButton.test.tsx +44 -0
- package/src/components/subModel/__tests__/utils.test.ts +24 -0
- package/src/components/subModel/utils.ts +6 -0
- package/src/data-source/index.ts +1 -1
- package/src/utils/index.ts +3 -0
- package/src/utils/randomId.ts +48 -0
|
@@ -260,6 +260,21 @@ const normalizeOpenKeys = /* @__PURE__ */ __name((nextOpenKeys) => {
|
|
|
260
260
|
}
|
|
261
261
|
return nextOpenKeys.filter((key) => latestKey === key || latestKey.startsWith(`${key}/`));
|
|
262
262
|
}, "normalizeOpenKeys");
|
|
263
|
+
const getLabelSearchText = /* @__PURE__ */ __name((label) => {
|
|
264
|
+
if (label === null || label === void 0 || typeof label === "boolean") {
|
|
265
|
+
return "";
|
|
266
|
+
}
|
|
267
|
+
if (typeof label === "string" || typeof label === "number") {
|
|
268
|
+
return String(label);
|
|
269
|
+
}
|
|
270
|
+
if (Array.isArray(label)) {
|
|
271
|
+
return label.map(getLabelSearchText).join(" ");
|
|
272
|
+
}
|
|
273
|
+
if (import_react.default.isValidElement(label)) {
|
|
274
|
+
return getLabelSearchText(label.props.children);
|
|
275
|
+
}
|
|
276
|
+
return "";
|
|
277
|
+
}, "getLabelSearchText");
|
|
263
278
|
const createSearchItem = /* @__PURE__ */ __name((item, searchKey, currentSearchValue, menuVisible, t, updateSearchValue) => ({
|
|
264
279
|
key: `${searchKey}-search`,
|
|
265
280
|
type: "group",
|
|
@@ -378,13 +393,9 @@ const LazyDropdown = /* @__PURE__ */ __name(({ menu, ...props }) => {
|
|
|
378
393
|
const currentSearchValue = searchValues[searchKey] || "";
|
|
379
394
|
const filteredChildren = currentSearchValue ? (/* @__PURE__ */ __name(function deepFilter(items2) {
|
|
380
395
|
const searchText = currentSearchValue.toLowerCase();
|
|
381
|
-
const tryString = /* @__PURE__ */ __name((v) => {
|
|
382
|
-
if (!v) return "";
|
|
383
|
-
return typeof v === "string" ? v : String(v);
|
|
384
|
-
}, "tryString");
|
|
385
396
|
return items2.map((child) => {
|
|
386
|
-
const labelStr =
|
|
387
|
-
const selfMatch = labelStr.includes(searchText)
|
|
397
|
+
const labelStr = getLabelSearchText(child.label).toLowerCase();
|
|
398
|
+
const selfMatch = labelStr.includes(searchText);
|
|
388
399
|
if (child.type === "group" && Array.isArray(child.children)) {
|
|
389
400
|
const nested = deepFilter(child.children);
|
|
390
401
|
if (selfMatch || nested.length > 0) {
|
|
@@ -172,7 +172,7 @@ function buildSubModelItems(subModelBaseClass, exclude = []) {
|
|
|
172
172
|
__name(buildSubModelItems, "buildSubModelItems");
|
|
173
173
|
function buildSubModelGroups(subModelBaseClasses = []) {
|
|
174
174
|
return async (ctx) => {
|
|
175
|
-
var _a, _b, _c;
|
|
175
|
+
var _a, _b, _c, _d, _e;
|
|
176
176
|
const items = [];
|
|
177
177
|
const exclude = [];
|
|
178
178
|
for (const subModelBaseClass of subModelBaseClasses) {
|
|
@@ -203,11 +203,15 @@ function buildSubModelGroups(subModelBaseClasses = []) {
|
|
|
203
203
|
const baseKey = typeof subModelBaseClass === "string" ? subModelBaseClass : BaseClass.name;
|
|
204
204
|
const menuType = ((_b = BaseClass == null ? void 0 : BaseClass.meta) == null ? void 0 : _b.menuType) || "group";
|
|
205
205
|
const groupSort = ((_c = BaseClass == null ? void 0 : BaseClass.meta) == null ? void 0 : _c.sort) ?? 1e3;
|
|
206
|
+
const searchable = !!((_d = BaseClass == null ? void 0 : BaseClass.meta) == null ? void 0 : _d.searchable);
|
|
207
|
+
const searchPlaceholder = (_e = BaseClass == null ? void 0 : BaseClass.meta) == null ? void 0 : _e.searchPlaceholder;
|
|
206
208
|
if (menuType === "submenu") {
|
|
207
209
|
items.push({
|
|
208
210
|
key: baseKey,
|
|
209
211
|
label: groupLabel,
|
|
210
212
|
sort: groupSort,
|
|
213
|
+
searchable,
|
|
214
|
+
searchPlaceholder,
|
|
211
215
|
children
|
|
212
216
|
});
|
|
213
217
|
} else {
|
|
@@ -216,6 +220,8 @@ function buildSubModelGroups(subModelBaseClasses = []) {
|
|
|
216
220
|
type: "group",
|
|
217
221
|
label: groupLabel,
|
|
218
222
|
sort: groupSort,
|
|
223
|
+
searchable,
|
|
224
|
+
searchPlaceholder,
|
|
219
225
|
children
|
|
220
226
|
});
|
|
221
227
|
}
|
package/lib/data-source/index.js
CHANGED
|
@@ -978,7 +978,7 @@ const _CollectionField = class _CollectionField {
|
|
|
978
978
|
{
|
|
979
979
|
...import_lodash.default.omit(((_a = this.options.uiSchema) == null ? void 0 : _a["x-component-props"]) || {}, "fieldNames"),
|
|
980
980
|
options: this.enum.length ? this.enum : void 0,
|
|
981
|
-
mode: this.
|
|
981
|
+
mode: this.interface === "multipleSelect" ? "multiple" : void 0,
|
|
982
982
|
multiple: target ? ["belongsToMany", "hasMany", "belongsToArray"].includes(type) : void 0,
|
|
983
983
|
maxCount: target && !["belongsToMany", "hasMany", "belongsToArray"].includes(type) ? 1 : void 0,
|
|
984
984
|
target,
|
package/lib/utils/index.d.ts
CHANGED
|
@@ -29,3 +29,4 @@ export { createEphemeralContext } from './createEphemeralContext';
|
|
|
29
29
|
export { pruneFilter } from './pruneFilter';
|
|
30
30
|
export { isBeforeRenderFlow } from './flows';
|
|
31
31
|
export { resolveModuleUrl, isCssFile } from './resolveModuleUrl';
|
|
32
|
+
export { randomId } from './randomId';
|
package/lib/utils/index.js
CHANGED
|
@@ -74,6 +74,7 @@ __export(utils_exports, {
|
|
|
74
74
|
prepareRunJsCode: () => import_runjsTemplateCompat.prepareRunJsCode,
|
|
75
75
|
preprocessRunJsTemplates: () => import_runjsTemplateCompat.preprocessRunJsTemplates,
|
|
76
76
|
pruneFilter: () => import_pruneFilter.pruneFilter,
|
|
77
|
+
randomId: () => import_randomId.randomId,
|
|
77
78
|
resolveCreateModelOptions: () => import_params_resolvers.resolveCreateModelOptions,
|
|
78
79
|
resolveCtxDatePath: () => import_dateVariable.resolveCtxDatePath,
|
|
79
80
|
resolveDefaultParams: () => import_params_resolvers.resolveDefaultParams,
|
|
@@ -115,6 +116,7 @@ var import_createEphemeralContext = require("./createEphemeralContext");
|
|
|
115
116
|
var import_pruneFilter = require("./pruneFilter");
|
|
116
117
|
var import_flows = require("./flows");
|
|
117
118
|
var import_resolveModuleUrl = require("./resolveModuleUrl");
|
|
119
|
+
var import_randomId = require("./randomId");
|
|
118
120
|
// Annotate the CommonJS export names for ESM import in node:
|
|
119
121
|
0 && (module.exports = {
|
|
120
122
|
BLOCK_GROUP_CONFIGS,
|
|
@@ -165,6 +167,7 @@ var import_resolveModuleUrl = require("./resolveModuleUrl");
|
|
|
165
167
|
prepareRunJsCode,
|
|
166
168
|
preprocessRunJsTemplates,
|
|
167
169
|
pruneFilter,
|
|
170
|
+
randomId,
|
|
168
171
|
resolveCreateModelOptions,
|
|
169
172
|
resolveCtxDatePath,
|
|
170
173
|
resolveDefaultParams,
|
|
@@ -0,0 +1,39 @@
|
|
|
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
|
+
* Generate a random base36 identifier with an optional semantic prefix.
|
|
11
|
+
*
|
|
12
|
+
* Equivalent in shape to v1's `uid()` from `@formily/shared` (11 chars
|
|
13
|
+
* of `[0-9a-z]`), with an opt-in prefix appended at the front. v2 forbids
|
|
14
|
+
* direct `@formily/*` imports in `src/client-v2/`, so this helper is the
|
|
15
|
+
* single substitute the rest of the codebase should reach for.
|
|
16
|
+
*
|
|
17
|
+
* Common semantic prefixes observed across the codebase — pass the one
|
|
18
|
+
* that matches your domain rather than relying on a default, so the
|
|
19
|
+
* intent is explicit at the call site:
|
|
20
|
+
*
|
|
21
|
+
* - `s_` — service / settings record (authenticators, channels, …)
|
|
22
|
+
* - `v_` — verifier / variable / LLM service
|
|
23
|
+
* - `f_` — field
|
|
24
|
+
* - `t_` — through table
|
|
25
|
+
*
|
|
26
|
+
* Example:
|
|
27
|
+
*
|
|
28
|
+
* ```ts
|
|
29
|
+
* import { randomId } from '@nocobase/flow-engine';
|
|
30
|
+
*
|
|
31
|
+
* name: randomId('s_'), // → 's_keeoaui1ubi'
|
|
32
|
+
* name: randomId('v_'), // → 'v_a8f3kp2x9qm'
|
|
33
|
+
* name: randomId(), // → 'a8f3kp2x9qm'
|
|
34
|
+
* ```
|
|
35
|
+
*
|
|
36
|
+
* Not cryptographically secure — uses `Math.random()`. Good enough for
|
|
37
|
+
* unique form names / schema keys, NOT for security tokens.
|
|
38
|
+
*/
|
|
39
|
+
export declare function randomId(prefix?: string, length?: number): string;
|
|
@@ -0,0 +1,45 @@
|
|
|
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
|
+
var __defProp = Object.defineProperty;
|
|
11
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
12
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
13
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
14
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
15
|
+
var __export = (target, all) => {
|
|
16
|
+
for (var name in all)
|
|
17
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
18
|
+
};
|
|
19
|
+
var __copyProps = (to, from, except, desc) => {
|
|
20
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
21
|
+
for (let key of __getOwnPropNames(from))
|
|
22
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
23
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
24
|
+
}
|
|
25
|
+
return to;
|
|
26
|
+
};
|
|
27
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
28
|
+
var randomId_exports = {};
|
|
29
|
+
__export(randomId_exports, {
|
|
30
|
+
randomId: () => randomId
|
|
31
|
+
});
|
|
32
|
+
module.exports = __toCommonJS(randomId_exports);
|
|
33
|
+
const CHARSET = "0123456789abcdefghijklmnopqrstuvwxyz";
|
|
34
|
+
function randomId(prefix = "", length = 11) {
|
|
35
|
+
let id = "";
|
|
36
|
+
for (let i = 0; i < length; i++) {
|
|
37
|
+
id += CHARSET[Math.random() * CHARSET.length | 0];
|
|
38
|
+
}
|
|
39
|
+
return `${prefix}${id}`;
|
|
40
|
+
}
|
|
41
|
+
__name(randomId, "randomId");
|
|
42
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
43
|
+
0 && (module.exports = {
|
|
44
|
+
randomId
|
|
45
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nocobase/flow-engine",
|
|
3
|
-
"version": "2.1.0-beta.
|
|
3
|
+
"version": "2.1.0-beta.36",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "A standalone flow engine for NocoBase, managing workflows, models, and actions.",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
"dependencies": {
|
|
9
9
|
"@formily/antd-v5": "1.x",
|
|
10
10
|
"@formily/reactive": "2.x",
|
|
11
|
-
"@nocobase/sdk": "2.1.0-beta.
|
|
12
|
-
"@nocobase/shared": "2.1.0-beta.
|
|
11
|
+
"@nocobase/sdk": "2.1.0-beta.36",
|
|
12
|
+
"@nocobase/shared": "2.1.0-beta.36",
|
|
13
13
|
"ahooks": "^3.7.2",
|
|
14
14
|
"axios": "^1.7.0",
|
|
15
15
|
"dayjs": "^1.11.9",
|
|
@@ -37,5 +37,5 @@
|
|
|
37
37
|
],
|
|
38
38
|
"author": "NocoBase Team",
|
|
39
39
|
"license": "Apache-2.0",
|
|
40
|
-
"gitHead": "
|
|
40
|
+
"gitHead": "397d45c744f6eb48b3a0cd785c87cbf1257c3513"
|
|
41
41
|
}
|
|
@@ -368,6 +368,22 @@ const normalizeOpenKeys = (nextOpenKeys: string[]) => {
|
|
|
368
368
|
return nextOpenKeys.filter((key) => latestKey === key || latestKey.startsWith(`${key}/`));
|
|
369
369
|
};
|
|
370
370
|
|
|
371
|
+
const getLabelSearchText = (label: React.ReactNode): string => {
|
|
372
|
+
if (label === null || label === undefined || typeof label === 'boolean') {
|
|
373
|
+
return '';
|
|
374
|
+
}
|
|
375
|
+
if (typeof label === 'string' || typeof label === 'number') {
|
|
376
|
+
return String(label);
|
|
377
|
+
}
|
|
378
|
+
if (Array.isArray(label)) {
|
|
379
|
+
return label.map(getLabelSearchText).join(' ');
|
|
380
|
+
}
|
|
381
|
+
if (React.isValidElement(label)) {
|
|
382
|
+
return getLabelSearchText(label.props.children);
|
|
383
|
+
}
|
|
384
|
+
return '';
|
|
385
|
+
};
|
|
386
|
+
|
|
371
387
|
const createSearchItem = (
|
|
372
388
|
item: Item,
|
|
373
389
|
searchKey: string,
|
|
@@ -532,15 +548,10 @@ const LazyDropdown: React.FC<Omit<DropdownProps, 'menu'> & { menu: LazyDropdownM
|
|
|
532
548
|
const filteredChildren = currentSearchValue
|
|
533
549
|
? (function deepFilter(items: Item[]): Item[] {
|
|
534
550
|
const searchText = currentSearchValue.toLowerCase();
|
|
535
|
-
const tryString = (v: any) => {
|
|
536
|
-
if (!v) return '';
|
|
537
|
-
return typeof v === 'string' ? v : String(v);
|
|
538
|
-
};
|
|
539
551
|
return items
|
|
540
552
|
.map((child) => {
|
|
541
|
-
const labelStr =
|
|
542
|
-
const selfMatch =
|
|
543
|
-
labelStr.includes(searchText) || (child.key && String(child.key).toLowerCase().includes(searchText));
|
|
553
|
+
const labelStr = getLabelSearchText(child.label).toLowerCase();
|
|
554
|
+
const selfMatch = labelStr.includes(searchText);
|
|
544
555
|
if (child.type === 'group' && Array.isArray(child.children)) {
|
|
545
556
|
const nested = deepFilter(child.children);
|
|
546
557
|
if (selfMatch || nested.length > 0) {
|
|
@@ -310,6 +310,50 @@ describe('transformItems - searchable flags', () => {
|
|
|
310
310
|
expect(submenu.searchPlaceholder).toBe('Search blocks');
|
|
311
311
|
expect(Array.isArray(submenu.children)).toBe(true);
|
|
312
312
|
});
|
|
313
|
+
|
|
314
|
+
it('filters searchable field menus by display label instead of item key', async () => {
|
|
315
|
+
const engine = new FlowEngine();
|
|
316
|
+
await engine.flowSettings.forceEnable();
|
|
317
|
+
const parent = engine.createModel<FlowModel>({ use: FlowModel });
|
|
318
|
+
const user = userEvent.setup();
|
|
319
|
+
|
|
320
|
+
const items = [
|
|
321
|
+
{
|
|
322
|
+
key: 'fields',
|
|
323
|
+
label: '',
|
|
324
|
+
type: 'group' as const,
|
|
325
|
+
searchable: true,
|
|
326
|
+
searchPlaceholder: 'Search fields',
|
|
327
|
+
children: [
|
|
328
|
+
{ key: 'field_name', label: 'Field display name' },
|
|
329
|
+
{ key: 'other_field', label: 'Other field' },
|
|
330
|
+
],
|
|
331
|
+
},
|
|
332
|
+
];
|
|
333
|
+
|
|
334
|
+
render(
|
|
335
|
+
<FlowEngineProvider engine={engine}>
|
|
336
|
+
<ConfigProvider>
|
|
337
|
+
<App>
|
|
338
|
+
<AddSubModelButton model={parent} items={items as any} subModelKey="items">
|
|
339
|
+
Open
|
|
340
|
+
</AddSubModelButton>
|
|
341
|
+
</App>
|
|
342
|
+
</ConfigProvider>
|
|
343
|
+
</FlowEngineProvider>,
|
|
344
|
+
);
|
|
345
|
+
|
|
346
|
+
await user.click(screen.getByText('Open'));
|
|
347
|
+
const searchInput = await screen.findByPlaceholderText('Search fields');
|
|
348
|
+
expect(screen.getByText('Field display name')).toBeInTheDocument();
|
|
349
|
+
|
|
350
|
+
await user.type(searchInput, 'field_name');
|
|
351
|
+
await waitFor(() => expect(screen.queryByText('Field display name')).not.toBeInTheDocument());
|
|
352
|
+
|
|
353
|
+
await user.clear(searchInput);
|
|
354
|
+
await user.type(searchInput, 'display');
|
|
355
|
+
await waitFor(() => expect(screen.getByText('Field display name')).toBeInTheDocument());
|
|
356
|
+
});
|
|
313
357
|
});
|
|
314
358
|
|
|
315
359
|
describe('transformItems - hide', () => {
|
|
@@ -100,6 +100,30 @@ describe('subModel/utils', () => {
|
|
|
100
100
|
expect(groups[0].children).toBeTruthy();
|
|
101
101
|
});
|
|
102
102
|
|
|
103
|
+
it('preserves searchable meta on generated groups', async () => {
|
|
104
|
+
const engine = new FlowEngine();
|
|
105
|
+
|
|
106
|
+
class Base extends FlowModel {}
|
|
107
|
+
Base.define({
|
|
108
|
+
label: 'Base Group',
|
|
109
|
+
searchable: true,
|
|
110
|
+
searchPlaceholder: 'Search fields',
|
|
111
|
+
});
|
|
112
|
+
const BaseDC = attachDefineChildren(Base, async () => [{ key: 'title', label: 'Title' }]);
|
|
113
|
+
|
|
114
|
+
engine.registerModels({ Base: BaseDC });
|
|
115
|
+
|
|
116
|
+
const model = engine.createModel({ use: 'FlowModel' });
|
|
117
|
+
const ctx = model.context;
|
|
118
|
+
|
|
119
|
+
const groupsFactory = buildSubModelGroups([BaseDC]);
|
|
120
|
+
const groups = await groupsFactory(ctx);
|
|
121
|
+
|
|
122
|
+
expect(groups).toHaveLength(1);
|
|
123
|
+
expect(groups[0].searchable).toBe(true);
|
|
124
|
+
expect(groups[0].searchPlaceholder).toBe('Search fields');
|
|
125
|
+
});
|
|
126
|
+
|
|
103
127
|
it('invokes buildSubModelItems when meta.children is false', async () => {
|
|
104
128
|
const engine = new FlowEngine();
|
|
105
129
|
|
|
@@ -196,12 +196,16 @@ export function buildSubModelGroups(subModelBaseClasses: (string | ModelConstruc
|
|
|
196
196
|
const baseKey = typeof subModelBaseClass === 'string' ? subModelBaseClass : BaseClass.name;
|
|
197
197
|
const menuType = BaseClass?.meta?.menuType || 'group';
|
|
198
198
|
const groupSort = BaseClass?.meta?.sort ?? 1000;
|
|
199
|
+
const searchable = !!BaseClass?.meta?.searchable;
|
|
200
|
+
const searchPlaceholder = BaseClass?.meta?.searchPlaceholder;
|
|
199
201
|
if (menuType === 'submenu') {
|
|
200
202
|
// 作为可点击的一级项,展开二级子菜单
|
|
201
203
|
items.push({
|
|
202
204
|
key: baseKey,
|
|
203
205
|
label: groupLabel,
|
|
204
206
|
sort: groupSort,
|
|
207
|
+
searchable,
|
|
208
|
+
searchPlaceholder,
|
|
205
209
|
children,
|
|
206
210
|
});
|
|
207
211
|
} else {
|
|
@@ -211,6 +215,8 @@ export function buildSubModelGroups(subModelBaseClasses: (string | ModelConstruc
|
|
|
211
215
|
type: 'group',
|
|
212
216
|
label: groupLabel,
|
|
213
217
|
sort: groupSort,
|
|
218
|
+
searchable,
|
|
219
|
+
searchPlaceholder,
|
|
214
220
|
children,
|
|
215
221
|
});
|
|
216
222
|
}
|
package/src/data-source/index.ts
CHANGED
|
@@ -1114,7 +1114,7 @@ export class CollectionField {
|
|
|
1114
1114
|
{
|
|
1115
1115
|
..._.omit(this.options.uiSchema?.['x-component-props'] || {}, 'fieldNames'),
|
|
1116
1116
|
options: this.enum.length ? this.enum : undefined,
|
|
1117
|
-
mode: this.
|
|
1117
|
+
mode: this.interface === 'multipleSelect' ? 'multiple' : undefined,
|
|
1118
1118
|
multiple: target ? ['belongsToMany', 'hasMany', 'belongsToArray'].includes(type) : undefined,
|
|
1119
1119
|
maxCount: target && !['belongsToMany', 'hasMany', 'belongsToArray'].includes(type) ? 1 : undefined,
|
|
1120
1120
|
target: target,
|
package/src/utils/index.ts
CHANGED
|
@@ -0,0 +1,48 @@
|
|
|
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
|
+
const CHARSET = '0123456789abcdefghijklmnopqrstuvwxyz';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Generate a random base36 identifier with an optional semantic prefix.
|
|
14
|
+
*
|
|
15
|
+
* Equivalent in shape to v1's `uid()` from `@formily/shared` (11 chars
|
|
16
|
+
* of `[0-9a-z]`), with an opt-in prefix appended at the front. v2 forbids
|
|
17
|
+
* direct `@formily/*` imports in `src/client-v2/`, so this helper is the
|
|
18
|
+
* single substitute the rest of the codebase should reach for.
|
|
19
|
+
*
|
|
20
|
+
* Common semantic prefixes observed across the codebase — pass the one
|
|
21
|
+
* that matches your domain rather than relying on a default, so the
|
|
22
|
+
* intent is explicit at the call site:
|
|
23
|
+
*
|
|
24
|
+
* - `s_` — service / settings record (authenticators, channels, …)
|
|
25
|
+
* - `v_` — verifier / variable / LLM service
|
|
26
|
+
* - `f_` — field
|
|
27
|
+
* - `t_` — through table
|
|
28
|
+
*
|
|
29
|
+
* Example:
|
|
30
|
+
*
|
|
31
|
+
* ```ts
|
|
32
|
+
* import { randomId } from '@nocobase/flow-engine';
|
|
33
|
+
*
|
|
34
|
+
* name: randomId('s_'), // → 's_keeoaui1ubi'
|
|
35
|
+
* name: randomId('v_'), // → 'v_a8f3kp2x9qm'
|
|
36
|
+
* name: randomId(), // → 'a8f3kp2x9qm'
|
|
37
|
+
* ```
|
|
38
|
+
*
|
|
39
|
+
* Not cryptographically secure — uses `Math.random()`. Good enough for
|
|
40
|
+
* unique form names / schema keys, NOT for security tokens.
|
|
41
|
+
*/
|
|
42
|
+
export function randomId(prefix = '', length = 11): string {
|
|
43
|
+
let id = '';
|
|
44
|
+
for (let i = 0; i < length; i++) {
|
|
45
|
+
id += CHARSET[(Math.random() * CHARSET.length) | 0];
|
|
46
|
+
}
|
|
47
|
+
return `${prefix}${id}`;
|
|
48
|
+
}
|