@nocobase/plugin-flow-engine 2.2.0-alpha.11 → 2.2.0-alpha.12
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/dist/ai/docs/runjs/context/ai.md +194 -4
- package/dist/externalVersion.js +9 -9
- package/dist/node_modules/@ant-design/icons-svg/package.json +1 -1
- package/dist/node_modules/acorn/package.json +1 -1
- package/dist/node_modules/acorn-jsx/package.json +1 -1
- package/dist/node_modules/acorn-walk/package.json +1 -1
- package/dist/node_modules/ses/package.json +1 -1
- package/dist/node_modules/zod/package.json +1 -1
- package/dist/server/flow-surfaces/navigation-targets.d.ts +7 -0
- package/dist/server/flow-surfaces/navigation-targets.js +161 -54
- package/dist/server/flow-surfaces/service.d.ts +7 -0
- package/dist/server/flow-surfaces/service.js +186 -53
- package/package.json +2 -2
|
@@ -1,23 +1,51 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: "ctx.ai"
|
|
3
|
-
description: "Use ctx.ai in RunJS to trigger AI employee tasks, either with inline task content or with tasks configured on an AI employee action."
|
|
4
|
-
keywords: "ctx.ai,AI employee,triggerTask,triggerModelTask,RunJS,NocoBase"
|
|
3
|
+
description: "Use ctx.ai in RunJS to trigger AI employee tasks in the global conversation or a specified AI Chat Box, either with inline task content or with tasks configured on an AI employee action."
|
|
4
|
+
keywords: "ctx.ai,AI employee,uploadFile,attachments,triggerTask,triggerModelTask,onResponseLoadingChange,chatBoxUid,AI Chat Box,RunJS,NocoBase"
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# ctx.ai
|
|
8
8
|
|
|
9
9
|
Use `ctx.ai` in RunJS to trigger **AI employee tasks**. It works well in JSBlock, JSAction, and other interactions where a button, form, or business flow needs to hand work to a specific AI employee.
|
|
10
10
|
|
|
11
|
-
`ctx.ai`
|
|
11
|
+
`ctx.ai` uploads AI task attachments and triggers tasks. File uploads can be awaited, but task triggering does not return the execution result of the AI employee task. After the call, the task enters the AI employee conversation flow, and the result is handled by the AI employee session.
|
|
12
12
|
|
|
13
13
|
:::warning Note
|
|
14
14
|
|
|
15
|
-
`ctx.ai` is provided by the AI plugin. If the AI plugin is not enabled, or the current RunJS environment has not loaded the corresponding client capability, `ctx.ai` may not exist. You can check `ctx.ai?.triggerTask
|
|
15
|
+
`ctx.ai` is provided by the AI plugin. If the AI plugin is not enabled, or the current RunJS environment has not loaded the corresponding client capability, `ctx.ai` may not exist. You can check `ctx.ai?.uploadFile`, `ctx.ai?.triggerTask`, or `ctx.ai?.triggerModelTask` before calling it.
|
|
16
16
|
|
|
17
17
|
:::
|
|
18
18
|
|
|
19
19
|
## Methods
|
|
20
20
|
|
|
21
|
+
### ctx.ai.uploadFile()
|
|
22
|
+
|
|
23
|
+
Upload one file and return an attachment object that can be passed directly to an AI employee task.
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
const attachment = await ctx.ai.uploadFile(file, options);
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
| Parameter | Type | Description |
|
|
30
|
+
|------|------|------|
|
|
31
|
+
| `file` | `File` | Browser file object to upload. |
|
|
32
|
+
| `options.onProgress` | `(percent: number) => void` | Upload progress callback. `percent` ranges from `0` to `100`. |
|
|
33
|
+
| `options.signal` | `AbortSignal` | Signal used to cancel the upload. |
|
|
34
|
+
|
|
35
|
+
The upload uses the file storage configured by the AI plugin and creates a record in `aiFiles`. The returned object includes fields such as `id`, `filename`, `url`, and `source`:
|
|
36
|
+
|
|
37
|
+
```ts
|
|
38
|
+
const attachment = await ctx.ai.uploadFile(file, {
|
|
39
|
+
onProgress(percent) {
|
|
40
|
+
console.log('upload progress', percent);
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// attachment can be placed directly in message.attachments
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
The Promise is rejected when the upload fails. Removing an attachment from the local UI does not delete the record already created in `aiFiles`, matching the behavior of the default AI chat window.
|
|
48
|
+
|
|
21
49
|
### ctx.ai.triggerTask()
|
|
22
50
|
|
|
23
51
|
Trigger an AI employee task directly.
|
|
@@ -30,8 +58,10 @@ ctx.ai.triggerTask(options: TriggerTaskOptions): void
|
|
|
30
58
|
|------|------|------|
|
|
31
59
|
| `aiEmployee` | `string \| AIEmployee` | AI employee. When a string is passed, NocoBase matches `AIEmployee.username` exactly, and the AI employee must be accessible to the current user. |
|
|
32
60
|
| `tasks` | `Task[]` | Tasks to trigger. |
|
|
61
|
+
| `chatBoxUid` | `string` | FlowModel uid of the AI Chat Box block that should receive the task. |
|
|
33
62
|
| `open` | `boolean` | Whether to open the AI employee conversation panel. |
|
|
34
63
|
| `auto` | `boolean` | Whether to use the auto-trigger semantics of an AI employee action. |
|
|
64
|
+
| `onResponseLoadingChange` | `(loading: boolean) => void` | Model response loading callback. It only runs when this task is sent automatically. |
|
|
35
65
|
|
|
36
66
|
Common `Task` fields:
|
|
37
67
|
|
|
@@ -40,12 +70,160 @@ Common `Task` fields:
|
|
|
40
70
|
| `title` | `string` | Task title. |
|
|
41
71
|
| `message.system` | `string` | System message, used to constrain the AI employee's role and output requirements. |
|
|
42
72
|
| `message.user` | `string` | User message, which is the main instruction for this task. |
|
|
73
|
+
| `message.attachments` | `Attachment[]` | Attachments used by the task, usually returned by `ctx.ai.uploadFile()`. |
|
|
43
74
|
| `message.workContext` | `ContextItem[]` | Page block context used by the task. |
|
|
44
75
|
| `autoSend` | `boolean` | Whether to send the task message automatically. |
|
|
45
76
|
| `webSearch` | `boolean` | Whether Web search is allowed for this task. |
|
|
46
77
|
| `model` | `{ llmService: string; model: string } \| null` | Model used by this task. |
|
|
47
78
|
| `skillSettings` | `SkillSettings` | Skills and tools available to this task. |
|
|
48
79
|
|
|
80
|
+
### Track the Response Loading State
|
|
81
|
+
|
|
82
|
+
Pass `onResponseLoadingChange` in the top-level options to track the model response loading state for this task. The callback receives `true` when NocoBase starts waiting for the model response, and `false` when the response completes, is canceled, or fails. If the React component has declared `setResponseLoading` with `useState`, you can write:
|
|
83
|
+
|
|
84
|
+
```tsx
|
|
85
|
+
ctx.ai.triggerTask({
|
|
86
|
+
aiEmployee: 'nathan',
|
|
87
|
+
open: true,
|
|
88
|
+
tasks: [
|
|
89
|
+
{
|
|
90
|
+
title: ctx.t('Review current page'),
|
|
91
|
+
message: {
|
|
92
|
+
user: 'Review the current page and summarize the main risks.',
|
|
93
|
+
},
|
|
94
|
+
autoSend: true,
|
|
95
|
+
},
|
|
96
|
+
],
|
|
97
|
+
onResponseLoadingChange(loading) {
|
|
98
|
+
setResponseLoading(loading);
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
`onResponseLoadingChange` only tracks the model response started directly by this `triggerTask()` call. With `autoSend: false`, the task is placed in the chat draft and the callback does not run. If the user sends the draft later, that manual send does not reuse this callback.
|
|
104
|
+
|
|
105
|
+
In a React component inside a JS block, this state update rerenders the component while it remains mounted.
|
|
106
|
+
|
|
107
|
+
### Target an AI Chat Box
|
|
108
|
+
|
|
109
|
+
Set `chatBoxUid` on the top-level `triggerTask()` options to trigger the task in a mounted AI Chat Box block instead of the global AI employee dialog.
|
|
110
|
+
|
|
111
|
+
```ts
|
|
112
|
+
ctx.ai.triggerTask({
|
|
113
|
+
aiEmployee: 'nathan',
|
|
114
|
+
chatBoxUid: 'AI_CHAT_BOX_BLOCK_UID',
|
|
115
|
+
open: true,
|
|
116
|
+
tasks: [
|
|
117
|
+
{
|
|
118
|
+
title: ctx.t('Review current page'),
|
|
119
|
+
message: {
|
|
120
|
+
user: 'Review the current page and summarize the main risks.',
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
],
|
|
124
|
+
});
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
The uid must belong to the outer AI Chat Box block currently mounted on the page. Do not put this routing value inside `tasks`. If the target block cannot be found, NocoBase reports an error and does not fall back to the global dialog. When `chatBoxUid` is omitted, the task uses the global AI employee dialog.
|
|
128
|
+
|
|
129
|
+
### Upload and Send Attachments in JSBlock
|
|
130
|
+
|
|
131
|
+
The following example renders file upload, task instructions, and a send button in JSBlock. Uploaded files are passed to the AI employee through `message.attachments`:
|
|
132
|
+
|
|
133
|
+
```tsx
|
|
134
|
+
if (!ctx.ai?.uploadFile || !ctx.ai?.triggerTask) {
|
|
135
|
+
ctx.message.error(ctx.t('AI employee task API is not available.'));
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const { React } = ctx.libs;
|
|
140
|
+
const { useState } = React;
|
|
141
|
+
const { Button, Card, Input, Space, Upload } = ctx.libs.antd;
|
|
142
|
+
const { InboxOutlined, SendOutlined } = ctx.libs.antdIcons;
|
|
143
|
+
|
|
144
|
+
const AttachmentTask = () => {
|
|
145
|
+
const [prompt, setPrompt] = useState('');
|
|
146
|
+
const [fileList, setFileList] = useState([]);
|
|
147
|
+
|
|
148
|
+
const uploadAttachment = async ({ file, onError, onProgress, onSuccess }) => {
|
|
149
|
+
try {
|
|
150
|
+
const attachment = await ctx.ai.uploadFile(file, {
|
|
151
|
+
onProgress(percent) {
|
|
152
|
+
onProgress?.({ percent });
|
|
153
|
+
},
|
|
154
|
+
});
|
|
155
|
+
onSuccess?.(attachment);
|
|
156
|
+
} catch (error) {
|
|
157
|
+
onError?.(error instanceof Error ? error : new Error(ctx.t('File upload failed')));
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
const sendTask = () => {
|
|
162
|
+
const attachments = fileList
|
|
163
|
+
.filter((file) => file.status === 'done' && file.response)
|
|
164
|
+
.map((file) => file.response);
|
|
165
|
+
|
|
166
|
+
if (!prompt.trim()) {
|
|
167
|
+
ctx.message.warning(ctx.t('Enter task instructions'));
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
ctx.ai.triggerTask({
|
|
172
|
+
aiEmployee: 'viz',
|
|
173
|
+
open: true,
|
|
174
|
+
tasks: [
|
|
175
|
+
{
|
|
176
|
+
title: ctx.t('Analyze uploaded files'),
|
|
177
|
+
message: {
|
|
178
|
+
user: prompt.trim(),
|
|
179
|
+
attachments,
|
|
180
|
+
},
|
|
181
|
+
autoSend: true,
|
|
182
|
+
},
|
|
183
|
+
],
|
|
184
|
+
});
|
|
185
|
+
setPrompt('');
|
|
186
|
+
setFileList([]);
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
const uploading = fileList.some((file) => file.status === 'uploading');
|
|
190
|
+
|
|
191
|
+
return (
|
|
192
|
+
<Card title={ctx.t('AI file analysis')}>
|
|
193
|
+
<Space direction="vertical" size="middle" style={{ width: '100%' }}>
|
|
194
|
+
<Upload.Dragger
|
|
195
|
+
multiple
|
|
196
|
+
fileList={fileList}
|
|
197
|
+
customRequest={uploadAttachment}
|
|
198
|
+
onChange={({ fileList: nextFileList }) => setFileList(nextFileList)}
|
|
199
|
+
>
|
|
200
|
+
<p className="ant-upload-drag-icon"><InboxOutlined /></p>
|
|
201
|
+
<p>{ctx.t('Click or drag files here to upload')}</p>
|
|
202
|
+
</Upload.Dragger>
|
|
203
|
+
<Input.TextArea
|
|
204
|
+
value={prompt}
|
|
205
|
+
onChange={(event) => setPrompt(event.target.value)}
|
|
206
|
+
placeholder={ctx.t('Describe the task for the AI employee')}
|
|
207
|
+
autoSize={{ minRows: 3, maxRows: 8 }}
|
|
208
|
+
/>
|
|
209
|
+
<Button
|
|
210
|
+
type="primary"
|
|
211
|
+
icon={<SendOutlined />}
|
|
212
|
+
disabled={uploading || !prompt.trim()}
|
|
213
|
+
onClick={sendTask}
|
|
214
|
+
>
|
|
215
|
+
{ctx.t('Send to AI')}
|
|
216
|
+
</Button>
|
|
217
|
+
</Space>
|
|
218
|
+
</Card>
|
|
219
|
+
);
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
ctx.render(<AttachmentTask />);
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
With `autoSend: false`, the attachments and task instructions are placed in the AI chat draft instead of being sent immediately.
|
|
226
|
+
|
|
49
227
|
### Add Page Block Context
|
|
50
228
|
|
|
51
229
|
`message.workContext` currently passes page blocks. Put the FlowModel uid of the target page block into it:
|
|
@@ -163,9 +341,15 @@ ctx.ai.triggerModelTask(uid: string, taskIndex: number, options?: TriggerModelTa
|
|
|
163
341
|
| `taskIndex` | `number` | Task index, starting from `0`. |
|
|
164
342
|
| `options.open` | `boolean` | Whether to open the AI employee conversation panel. |
|
|
165
343
|
| `options.auto` | `boolean` | Whether to use the auto-trigger semantics of an AI employee action. |
|
|
344
|
+
| `options.attachments` | `Attachment[]` | Attachments dynamically appended to the configured task. |
|
|
345
|
+
| `options.onResponseLoadingChange` | `(loading: boolean) => void` | Model response loading callback. It only runs when the configured task is sent automatically. |
|
|
166
346
|
|
|
167
347
|
This method reads the AI employee and task configuration from the target model. It is useful when the task has already been configured on an AI employee action on the page, and RunJS only needs to trigger it.
|
|
168
348
|
|
|
349
|
+
The public `triggerModelTask()` options do not accept `chatBoxUid`. To target an AI Chat Box, configure `chatBoxUid` on the preset task of the AI employee action. `triggerModelTask()` continues to reuse that preset value.
|
|
350
|
+
|
|
351
|
+
`options.onResponseLoadingChange` behaves the same as the option on `triggerTask()`. Whether it runs depends on the configured task's `autoSend` value. It does not run when that task uses `autoSend: false`.
|
|
352
|
+
|
|
169
353
|
```ts
|
|
170
354
|
if (!ctx.ai?.triggerModelTask) {
|
|
171
355
|
ctx.message.error(ctx.t('AI employee task API is not available.'));
|
|
@@ -176,6 +360,7 @@ const weeklyReviewActionUid = 'AI_EMPLOYEE_ACTION_MODEL_UID';
|
|
|
176
360
|
|
|
177
361
|
ctx.ai.triggerModelTask(weeklyReviewActionUid, 0, {
|
|
178
362
|
open: true,
|
|
363
|
+
attachments,
|
|
179
364
|
});
|
|
180
365
|
|
|
181
366
|
ctx.message.success(ctx.t('Configured AI employee task triggered.'));
|
|
@@ -195,9 +380,14 @@ If the target model does not exist, has no AI employee configured, or the specif
|
|
|
195
380
|
## Notes
|
|
196
381
|
|
|
197
382
|
- `triggerTask()` and `triggerModelTask()` are fire-and-forget. They do not return the execution result of the AI employee task.
|
|
383
|
+
- `uploadFile()` returns a Promise. Wait for the upload to finish before triggering a task that uses the attachment.
|
|
198
384
|
- `aiEmployee` strings only match `AIEmployee.username` exactly. They do not match nicknames, job titles, or translated names.
|
|
199
385
|
- `triggerModelTask()` uses a `0`-based `taskIndex`.
|
|
200
386
|
- `triggerModelTask()` reads task configuration from the target AI employee action model. If the task needs work context, configure `message.workContext` on that task.
|
|
387
|
+
- Top-level `triggerTask().chatBoxUid` must reference an AI Chat Box block currently mounted on the page.
|
|
388
|
+
- `triggerModelTask()` keeps using `chatBoxUid` configured on its preset task.
|
|
389
|
+
- Dynamic `triggerModelTask()` attachments are appended to the preset task's existing `message.attachments` without changing the saved task configuration.
|
|
390
|
+
- `onResponseLoadingChange` only tracks a model response automatically sent by the current call. It does not track a message the user sends manually later.
|
|
201
391
|
|
|
202
392
|
## Related
|
|
203
393
|
|
package/dist/externalVersion.js
CHANGED
|
@@ -8,16 +8,16 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
module.exports = {
|
|
11
|
-
"@nocobase/client": "2.2.0-alpha.
|
|
11
|
+
"@nocobase/client": "2.2.0-alpha.12",
|
|
12
12
|
"lodash": "4.18.1",
|
|
13
|
-
"@nocobase/database": "2.2.0-alpha.
|
|
14
|
-
"@nocobase/data-source-manager": "2.2.0-alpha.
|
|
15
|
-
"@nocobase/resourcer": "2.2.0-alpha.
|
|
16
|
-
"@nocobase/utils": "2.2.0-alpha.
|
|
17
|
-
"@nocobase/cache": "2.2.0-alpha.
|
|
18
|
-
"@nocobase/server": "2.2.0-alpha.
|
|
19
|
-
"@nocobase/actions": "2.2.0-alpha.
|
|
13
|
+
"@nocobase/database": "2.2.0-alpha.12",
|
|
14
|
+
"@nocobase/data-source-manager": "2.2.0-alpha.12",
|
|
15
|
+
"@nocobase/resourcer": "2.2.0-alpha.12",
|
|
16
|
+
"@nocobase/utils": "2.2.0-alpha.12",
|
|
17
|
+
"@nocobase/cache": "2.2.0-alpha.12",
|
|
18
|
+
"@nocobase/server": "2.2.0-alpha.12",
|
|
19
|
+
"@nocobase/actions": "2.2.0-alpha.12",
|
|
20
20
|
"sequelize": "6.35.2",
|
|
21
21
|
"@formily/json-schema": "2.3.7",
|
|
22
|
-
"@nocobase/ai": "2.2.0-alpha.
|
|
22
|
+
"@nocobase/ai": "2.2.0-alpha.12"
|
|
23
23
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"name":"@ant-design/icons-svg","version":"4.3.1","sideEffects":false,"description":"Abstract nodes for ant design icons.","contributors":["HeskeyBaozi <hezhiyu233@foxmail.com>"],"homepage":"https://github.com/ant-design/ant-design-icons/","license":"MIT","main":"lib/index.js","module":"es/index.js","files":["lib","es","inline-svg","inline-namespaced-svg"],"repository":{"type":"git","url":"git+https://github.com/ant-design/ant-design-icons.git"},"scripts":{"g":"npm run generate","generate":"cross-env NODE_ENV=production gulp --require ts-node/register/transpile-only","type-check":"tsc --noEmit","build:es":"tsc --project tsconfig.build.json --module esnext --outDir es","build:lib":"tsc --project tsconfig.build.json --module commonjs --outDir lib","build":"cross-env NODE_ENV=production && npm run build:es && npm run build:lib","test":"cross-env NODE_ENV=test && npm run test:unit","test:unit":"jest","prepublishOnly":"npm run test && npm run g && npm run build && npm run type-check && npm run digest","digest":"node scripts/digest.js"},"bugs":{"url":"https://github.com/ant-design/ant-design-icons/issues"},"devDependencies":{"@babel/core":"^7.8.6","@babel/preset-env":"^7.8.6","@babel/preset-typescript":"^7.8.3","@rgrove/parse-xml":"^2.0.2","@types/gulp":"^4.0.6","@types/gulp-concat":"^0.0.32","@types/gulp-rename":"^0.0.33","@types/jest":"^25.1.3","@types/lodash.camelcase":"^4.3.6","@types/lodash.template":"^4.4.6","@types/lodash.upperfirst":"^4.3.6","@types/node":"^13.9.3","@types/ramda":"^0.26.43","@types/rgrove__parse-xml":"^1.1.0","@types/svgo":"^1.3.1","@types/through2":"^2.0.34","@typescript-eslint/eslint-plugin":"^2.21.0","@typescript-eslint/parser":"^2.21.0","babel-jest":"^25.1.0","cross-env":"^7.0.0","del":"^5.1.0","eslint":"^7.16.0","globby":"^11.0.0","gulp":"^4.0.2","gulp-clone":"^2.0.1","gulp-concat":"^2.6.1","gulp-header":"^2.0.9","gulp-rename":"^2.0.0","jest":"^25.1.0","lodash.camelcase":"^4.3.0","lodash.template":"^4.5.0","lodash.upperfirst":"^4.3.1","ramda":"^0.27.0","svgo":"^1.3.2","through2":"^3.0.1","ts-node":"^8.6.2","typescript":"^4.0.0","vinyl":"^2.2.0"},"publishConfig":{"access":"public","registry":"https://registry.npmjs.org"},"_lastModified":"2026-07-
|
|
1
|
+
{"name":"@ant-design/icons-svg","version":"4.3.1","sideEffects":false,"description":"Abstract nodes for ant design icons.","contributors":["HeskeyBaozi <hezhiyu233@foxmail.com>"],"homepage":"https://github.com/ant-design/ant-design-icons/","license":"MIT","main":"lib/index.js","module":"es/index.js","files":["lib","es","inline-svg","inline-namespaced-svg"],"repository":{"type":"git","url":"git+https://github.com/ant-design/ant-design-icons.git"},"scripts":{"g":"npm run generate","generate":"cross-env NODE_ENV=production gulp --require ts-node/register/transpile-only","type-check":"tsc --noEmit","build:es":"tsc --project tsconfig.build.json --module esnext --outDir es","build:lib":"tsc --project tsconfig.build.json --module commonjs --outDir lib","build":"cross-env NODE_ENV=production && npm run build:es && npm run build:lib","test":"cross-env NODE_ENV=test && npm run test:unit","test:unit":"jest","prepublishOnly":"npm run test && npm run g && npm run build && npm run type-check && npm run digest","digest":"node scripts/digest.js"},"bugs":{"url":"https://github.com/ant-design/ant-design-icons/issues"},"devDependencies":{"@babel/core":"^7.8.6","@babel/preset-env":"^7.8.6","@babel/preset-typescript":"^7.8.3","@rgrove/parse-xml":"^2.0.2","@types/gulp":"^4.0.6","@types/gulp-concat":"^0.0.32","@types/gulp-rename":"^0.0.33","@types/jest":"^25.1.3","@types/lodash.camelcase":"^4.3.6","@types/lodash.template":"^4.4.6","@types/lodash.upperfirst":"^4.3.6","@types/node":"^13.9.3","@types/ramda":"^0.26.43","@types/rgrove__parse-xml":"^1.1.0","@types/svgo":"^1.3.1","@types/through2":"^2.0.34","@typescript-eslint/eslint-plugin":"^2.21.0","@typescript-eslint/parser":"^2.21.0","babel-jest":"^25.1.0","cross-env":"^7.0.0","del":"^5.1.0","eslint":"^7.16.0","globby":"^11.0.0","gulp":"^4.0.2","gulp-clone":"^2.0.1","gulp-concat":"^2.6.1","gulp-header":"^2.0.9","gulp-rename":"^2.0.0","jest":"^25.1.0","lodash.camelcase":"^4.3.0","lodash.template":"^4.5.0","lodash.upperfirst":"^4.3.1","ramda":"^0.27.0","svgo":"^1.3.2","through2":"^3.0.1","ts-node":"^8.6.2","typescript":"^4.0.0","vinyl":"^2.2.0"},"publishConfig":{"access":"public","registry":"https://registry.npmjs.org"},"_lastModified":"2026-07-30T03:54:05.520Z"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"name":"acorn","description":"ECMAScript parser","homepage":"https://github.com/acornjs/acorn","main":"dist/acorn.js","types":"dist/acorn.d.ts","module":"dist/acorn.mjs","exports":{".":[{"import":"./dist/acorn.mjs","require":"./dist/acorn.js","default":"./dist/acorn.js"},"./dist/acorn.js"],"./package.json":"./package.json"},"version":"8.11.2","engines":{"node":">=0.4.0"},"maintainers":[{"name":"Marijn Haverbeke","email":"marijnh@gmail.com","web":"https://marijnhaverbeke.nl"},{"name":"Ingvar Stepanyan","email":"me@rreverser.com","web":"https://rreverser.com/"},{"name":"Adrian Heine","web":"http://adrianheine.de"}],"repository":{"type":"git","url":"https://github.com/acornjs/acorn.git"},"license":"MIT","scripts":{"prepare":"cd ..; npm run build:main"},"bin":{"acorn":"./bin/acorn"},"_lastModified":"2026-07-
|
|
1
|
+
{"name":"acorn","description":"ECMAScript parser","homepage":"https://github.com/acornjs/acorn","main":"dist/acorn.js","types":"dist/acorn.d.ts","module":"dist/acorn.mjs","exports":{".":[{"import":"./dist/acorn.mjs","require":"./dist/acorn.js","default":"./dist/acorn.js"},"./dist/acorn.js"],"./package.json":"./package.json"},"version":"8.11.2","engines":{"node":">=0.4.0"},"maintainers":[{"name":"Marijn Haverbeke","email":"marijnh@gmail.com","web":"https://marijnhaverbeke.nl"},{"name":"Ingvar Stepanyan","email":"me@rreverser.com","web":"https://rreverser.com/"},{"name":"Adrian Heine","web":"http://adrianheine.de"}],"repository":{"type":"git","url":"https://github.com/acornjs/acorn.git"},"license":"MIT","scripts":{"prepare":"cd ..; npm run build:main"},"bin":{"acorn":"./bin/acorn"},"_lastModified":"2026-07-30T03:54:06.651Z"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"name":"acorn-jsx","description":"Modern, fast React.js JSX parser","homepage":"https://github.com/acornjs/acorn-jsx","version":"5.3.2","maintainers":[{"name":"Ingvar Stepanyan","email":"me@rreverser.com","web":"http://rreverser.com/"}],"repository":{"type":"git","url":"https://github.com/acornjs/acorn-jsx"},"license":"MIT","scripts":{"test":"node test/run.js"},"peerDependencies":{"acorn":"^6.0.0 || ^7.0.0 || ^8.0.0"},"devDependencies":{"acorn":"^8.0.1"},"_lastModified":"2026-07-
|
|
1
|
+
{"name":"acorn-jsx","description":"Modern, fast React.js JSX parser","homepage":"https://github.com/acornjs/acorn-jsx","version":"5.3.2","maintainers":[{"name":"Ingvar Stepanyan","email":"me@rreverser.com","web":"http://rreverser.com/"}],"repository":{"type":"git","url":"https://github.com/acornjs/acorn-jsx"},"license":"MIT","scripts":{"test":"node test/run.js"},"peerDependencies":{"acorn":"^6.0.0 || ^7.0.0 || ^8.0.0"},"devDependencies":{"acorn":"^8.0.1"},"_lastModified":"2026-07-30T03:54:07.073Z"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"name":"acorn-walk","description":"ECMAScript (ESTree) AST walker","homepage":"https://github.com/acornjs/acorn","main":"dist/walk.js","types":"dist/walk.d.ts","module":"dist/walk.mjs","exports":{".":[{"import":"./dist/walk.mjs","require":"./dist/walk.js","default":"./dist/walk.js"},"./dist/walk.js"],"./package.json":"./package.json"},"version":"8.3.4","engines":{"node":">=0.4.0"},"dependencies":{"acorn":"^8.11.0"},"maintainers":[{"name":"Marijn Haverbeke","email":"marijnh@gmail.com","web":"https://marijnhaverbeke.nl"},{"name":"Ingvar Stepanyan","email":"me@rreverser.com","web":"https://rreverser.com/"},{"name":"Adrian Heine","web":"http://adrianheine.de"}],"repository":{"type":"git","url":"https://github.com/acornjs/acorn.git"},"scripts":{"prepare":"cd ..; npm run build:walk"},"license":"MIT","_lastModified":"2026-07-
|
|
1
|
+
{"name":"acorn-walk","description":"ECMAScript (ESTree) AST walker","homepage":"https://github.com/acornjs/acorn","main":"dist/walk.js","types":"dist/walk.d.ts","module":"dist/walk.mjs","exports":{".":[{"import":"./dist/walk.mjs","require":"./dist/walk.js","default":"./dist/walk.js"},"./dist/walk.js"],"./package.json":"./package.json"},"version":"8.3.4","engines":{"node":">=0.4.0"},"dependencies":{"acorn":"^8.11.0"},"maintainers":[{"name":"Marijn Haverbeke","email":"marijnh@gmail.com","web":"https://marijnhaverbeke.nl"},{"name":"Ingvar Stepanyan","email":"me@rreverser.com","web":"https://rreverser.com/"},{"name":"Adrian Heine","web":"http://adrianheine.de"}],"repository":{"type":"git","url":"https://github.com/acornjs/acorn.git"},"scripts":{"prepare":"cd ..; npm run build:walk"},"license":"MIT","_lastModified":"2026-07-30T03:54:07.185Z"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"name":"ses","version":"1.14.0","description":"Hardened JavaScript for Fearless Cooperation","keywords":["lockdown","harden","Compartment","assert","security","confinement","isolation","object capabilities","ocaps","secure execution","third-party code","prototype pollution","supply-chain attack","plugin"],"author":"Agoric","license":"Apache-2.0","homepage":"https://github.com/Agoric/SES-shim/tree/master/packages/ses#readme","repository":{"type":"git","url":"git+https://github.com/endojs/endo.git","directory":"packages/ses"},"bugs":{"url":"https://github.com/endojs/endo/issues"},"type":"module","main":"./dist/ses.cjs","module":"./index.js","unpkg":"./dist/ses.umd.js","types":"./types.d.ts","exports":{".":{"import":{"types":"./types.d.ts","xs":"./src-xs/index.js","default":"./index.js"},"require":{"types":"./dist/types.d.cts","default":"./dist/ses.cjs"}},"./lockdown":{"import":{"types":"./types.d.ts","default":"./index.js"},"require":{"types":"./dist/types.d.cts","default":"./dist/ses.cjs"}},"./hermes":{"require":{"types":"./dist/types.d.cts","default":"./dist/ses-hermes.cjs"}},"./tools.js":"./tools.js","./assert-shim.js":"./assert-shim.js","./lockdown-shim.js":{"xs":"./src-xs/lockdown-shim.js","default":"./lockdown-shim.js"},"./compartment-shim.js":{"xs":"./src-xs/compartment-shim.js","default":"./compartment-shim.js"},"./console-shim.js":"./console-shim.js","./package.json":"./package.json"},"scripts":{"build:vanilla":"node scripts/bundle.js","build:hermes":"node scripts/bundle.js hermes","build":"yarn build:vanilla && yarn build:hermes","clean":"rm -rf dist","cover":"c8 ava","demo":"python3 -m http.server","lint":"yarn lint:types && yarn lint:eslint","lint-fix":"eslint --fix .","lint:eslint":"eslint .","lint:types":"tsc","prepare":"npm run clean && npm run build","qt":"ava","test":"tsd && ava","test:hermes":"./scripts/hermes-test.sh","test:xs":"xst dist/ses.umd.js test/_lockdown-safe.js && node scripts/generate-test-xs.js && xst tmp/test-xs.js && rm -rf tmp","postpack":"git clean -fX \"*.d.ts*\" \"*.d.cts*\" \"*.d.mts*\" \"*.tsbuildinfo\""},"dependencies":{"@endo/cache-map":"^1.1.0","@endo/env-options":"^1.1.11","@endo/immutable-arraybuffer":"^1.1.2"},"devDependencies":{"@babel/generator":"^7.26.3","@babel/parser":"~7.26.2","@babel/traverse":"~7.25.9","@babel/types":"~7.26.0","@endo/compartment-mapper":"^1.6.3","@endo/module-source":"^1.3.3","@endo/test262-runner":"^0.1.48","@types/babel__traverse":"^7.20.5","ava":"^6.1.3","babel-eslint":"^10.1.0","c8":"^7.14.0","core-js":"^3.31.0","eslint":"^8.57.1","eslint-config-airbnb-base":"^15.0.0","eslint-config-prettier":"^9.1.0","eslint-plugin-eslint-comments":"^3.2.0","eslint-plugin-import":"^2.31.0","hermes-engine-cli":"^0.12.0","prettier":"^3.5.3","terser":"^5.16.6","tsd":"^0.31.2","typescript":"~5.8.3"},"files":["./*.d.ts","./*.js","./*.map","LICENSE*","SECURITY*","dist","lib","src","tools"],"publishConfig":{"access":"public"},"eslintConfig":{"extends":["plugin:@endo/ses"]},"ava":{"files":["test/**/*.test.*"],"timeout":"2m"},"typeCoverage":{"atLeast":81.17},"gitHead":"9815aea9541f241389d2135c6097a7442bdffa17","_lastModified":"2026-07-
|
|
1
|
+
{"name":"ses","version":"1.14.0","description":"Hardened JavaScript for Fearless Cooperation","keywords":["lockdown","harden","Compartment","assert","security","confinement","isolation","object capabilities","ocaps","secure execution","third-party code","prototype pollution","supply-chain attack","plugin"],"author":"Agoric","license":"Apache-2.0","homepage":"https://github.com/Agoric/SES-shim/tree/master/packages/ses#readme","repository":{"type":"git","url":"git+https://github.com/endojs/endo.git","directory":"packages/ses"},"bugs":{"url":"https://github.com/endojs/endo/issues"},"type":"module","main":"./dist/ses.cjs","module":"./index.js","unpkg":"./dist/ses.umd.js","types":"./types.d.ts","exports":{".":{"import":{"types":"./types.d.ts","xs":"./src-xs/index.js","default":"./index.js"},"require":{"types":"./dist/types.d.cts","default":"./dist/ses.cjs"}},"./lockdown":{"import":{"types":"./types.d.ts","default":"./index.js"},"require":{"types":"./dist/types.d.cts","default":"./dist/ses.cjs"}},"./hermes":{"require":{"types":"./dist/types.d.cts","default":"./dist/ses-hermes.cjs"}},"./tools.js":"./tools.js","./assert-shim.js":"./assert-shim.js","./lockdown-shim.js":{"xs":"./src-xs/lockdown-shim.js","default":"./lockdown-shim.js"},"./compartment-shim.js":{"xs":"./src-xs/compartment-shim.js","default":"./compartment-shim.js"},"./console-shim.js":"./console-shim.js","./package.json":"./package.json"},"scripts":{"build:vanilla":"node scripts/bundle.js","build:hermes":"node scripts/bundle.js hermes","build":"yarn build:vanilla && yarn build:hermes","clean":"rm -rf dist","cover":"c8 ava","demo":"python3 -m http.server","lint":"yarn lint:types && yarn lint:eslint","lint-fix":"eslint --fix .","lint:eslint":"eslint .","lint:types":"tsc","prepare":"npm run clean && npm run build","qt":"ava","test":"tsd && ava","test:hermes":"./scripts/hermes-test.sh","test:xs":"xst dist/ses.umd.js test/_lockdown-safe.js && node scripts/generate-test-xs.js && xst tmp/test-xs.js && rm -rf tmp","postpack":"git clean -fX \"*.d.ts*\" \"*.d.cts*\" \"*.d.mts*\" \"*.tsbuildinfo\""},"dependencies":{"@endo/cache-map":"^1.1.0","@endo/env-options":"^1.1.11","@endo/immutable-arraybuffer":"^1.1.2"},"devDependencies":{"@babel/generator":"^7.26.3","@babel/parser":"~7.26.2","@babel/traverse":"~7.25.9","@babel/types":"~7.26.0","@endo/compartment-mapper":"^1.6.3","@endo/module-source":"^1.3.3","@endo/test262-runner":"^0.1.48","@types/babel__traverse":"^7.20.5","ava":"^6.1.3","babel-eslint":"^10.1.0","c8":"^7.14.0","core-js":"^3.31.0","eslint":"^8.57.1","eslint-config-airbnb-base":"^15.0.0","eslint-config-prettier":"^9.1.0","eslint-plugin-eslint-comments":"^3.2.0","eslint-plugin-import":"^2.31.0","hermes-engine-cli":"^0.12.0","prettier":"^3.5.3","terser":"^5.16.6","tsd":"^0.31.2","typescript":"~5.8.3"},"files":["./*.d.ts","./*.js","./*.map","LICENSE*","SECURITY*","dist","lib","src","tools"],"publishConfig":{"access":"public"},"eslintConfig":{"extends":["plugin:@endo/ses"]},"ava":{"files":["test/**/*.test.*"],"timeout":"2m"},"typeCoverage":{"atLeast":81.17},"gitHead":"9815aea9541f241389d2135c6097a7442bdffa17","_lastModified":"2026-07-30T03:54:06.206Z"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"name":"zod","version":"4.3.5","type":"module","license":"MIT","author":"Colin McDonnell <zod@colinhacks.com>","description":"TypeScript-first schema declaration and validation library with static type inference","homepage":"https://zod.dev","llms":"https://zod.dev/llms.txt","llmsFull":"https://zod.dev/llms-full.txt","mcpServer":"https://mcp.inkeep.com/zod/mcp","funding":"https://github.com/sponsors/colinhacks","sideEffects":false,"files":["src","**/*.js","**/*.mjs","**/*.cjs","**/*.d.ts","**/*.d.mts","**/*.d.cts","**/package.json"],"keywords":["typescript","schema","validation","type","inference"],"main":"./index.cjs","types":"./index.d.cts","module":"./index.js","zshy":{"exports":{"./package.json":"./package.json",".":"./src/index.ts","./mini":"./src/mini/index.ts","./locales":"./src/locales/index.ts","./v3":"./src/v3/index.ts","./v4":"./src/v4/index.ts","./v4-mini":"./src/v4-mini/index.ts","./v4/mini":"./src/v4/mini/index.ts","./v4/core":"./src/v4/core/index.ts","./v4/locales":"./src/v4/locales/index.ts","./v4/locales/*":"./src/v4/locales/*"},"conditions":{"@zod/source":"src"}},"exports":{"./package.json":"./package.json",".":{"@zod/source":"./src/index.ts","types":"./index.d.cts","import":"./index.js","require":"./index.cjs"},"./mini":{"@zod/source":"./src/mini/index.ts","types":"./mini/index.d.cts","import":"./mini/index.js","require":"./mini/index.cjs"},"./locales":{"@zod/source":"./src/locales/index.ts","types":"./locales/index.d.cts","import":"./locales/index.js","require":"./locales/index.cjs"},"./v3":{"@zod/source":"./src/v3/index.ts","types":"./v3/index.d.cts","import":"./v3/index.js","require":"./v3/index.cjs"},"./v4":{"@zod/source":"./src/v4/index.ts","types":"./v4/index.d.cts","import":"./v4/index.js","require":"./v4/index.cjs"},"./v4-mini":{"@zod/source":"./src/v4-mini/index.ts","types":"./v4-mini/index.d.cts","import":"./v4-mini/index.js","require":"./v4-mini/index.cjs"},"./v4/mini":{"@zod/source":"./src/v4/mini/index.ts","types":"./v4/mini/index.d.cts","import":"./v4/mini/index.js","require":"./v4/mini/index.cjs"},"./v4/core":{"@zod/source":"./src/v4/core/index.ts","types":"./v4/core/index.d.cts","import":"./v4/core/index.js","require":"./v4/core/index.cjs"},"./v4/locales":{"@zod/source":"./src/v4/locales/index.ts","types":"./v4/locales/index.d.cts","import":"./v4/locales/index.js","require":"./v4/locales/index.cjs"},"./v4/locales/*":{"@zod/source":"./src/v4/locales/*","types":"./v4/locales/*","import":"./v4/locales/*","require":"./v4/locales/*"}},"repository":{"type":"git","url":"git+https://github.com/colinhacks/zod.git"},"bugs":{"url":"https://github.com/colinhacks/zod/issues"},"support":{"backing":{"npm-funding":true}},"scripts":{"clean":"git clean -xdf . -e node_modules","build":"zshy --project tsconfig.build.json","postbuild":"tsx ../../scripts/write-stub-package-jsons.ts && pnpm biome check --write .","test:watch":"pnpm vitest","test":"pnpm vitest run","prepublishOnly":"tsx ../../scripts/check-versions.ts"},"_lastModified":"2026-07-
|
|
1
|
+
{"name":"zod","version":"4.3.5","type":"module","license":"MIT","author":"Colin McDonnell <zod@colinhacks.com>","description":"TypeScript-first schema declaration and validation library with static type inference","homepage":"https://zod.dev","llms":"https://zod.dev/llms.txt","llmsFull":"https://zod.dev/llms-full.txt","mcpServer":"https://mcp.inkeep.com/zod/mcp","funding":"https://github.com/sponsors/colinhacks","sideEffects":false,"files":["src","**/*.js","**/*.mjs","**/*.cjs","**/*.d.ts","**/*.d.mts","**/*.d.cts","**/package.json"],"keywords":["typescript","schema","validation","type","inference"],"main":"./index.cjs","types":"./index.d.cts","module":"./index.js","zshy":{"exports":{"./package.json":"./package.json",".":"./src/index.ts","./mini":"./src/mini/index.ts","./locales":"./src/locales/index.ts","./v3":"./src/v3/index.ts","./v4":"./src/v4/index.ts","./v4-mini":"./src/v4-mini/index.ts","./v4/mini":"./src/v4/mini/index.ts","./v4/core":"./src/v4/core/index.ts","./v4/locales":"./src/v4/locales/index.ts","./v4/locales/*":"./src/v4/locales/*"},"conditions":{"@zod/source":"src"}},"exports":{"./package.json":"./package.json",".":{"@zod/source":"./src/index.ts","types":"./index.d.cts","import":"./index.js","require":"./index.cjs"},"./mini":{"@zod/source":"./src/mini/index.ts","types":"./mini/index.d.cts","import":"./mini/index.js","require":"./mini/index.cjs"},"./locales":{"@zod/source":"./src/locales/index.ts","types":"./locales/index.d.cts","import":"./locales/index.js","require":"./locales/index.cjs"},"./v3":{"@zod/source":"./src/v3/index.ts","types":"./v3/index.d.cts","import":"./v3/index.js","require":"./v3/index.cjs"},"./v4":{"@zod/source":"./src/v4/index.ts","types":"./v4/index.d.cts","import":"./v4/index.js","require":"./v4/index.cjs"},"./v4-mini":{"@zod/source":"./src/v4-mini/index.ts","types":"./v4-mini/index.d.cts","import":"./v4-mini/index.js","require":"./v4-mini/index.cjs"},"./v4/mini":{"@zod/source":"./src/v4/mini/index.ts","types":"./v4/mini/index.d.cts","import":"./v4/mini/index.js","require":"./v4/mini/index.cjs"},"./v4/core":{"@zod/source":"./src/v4/core/index.ts","types":"./v4/core/index.d.cts","import":"./v4/core/index.js","require":"./v4/core/index.cjs"},"./v4/locales":{"@zod/source":"./src/v4/locales/index.ts","types":"./v4/locales/index.d.cts","import":"./v4/locales/index.js","require":"./v4/locales/index.cjs"},"./v4/locales/*":{"@zod/source":"./src/v4/locales/*","types":"./v4/locales/*","import":"./v4/locales/*","require":"./v4/locales/*"}},"repository":{"type":"git","url":"git+https://github.com/colinhacks/zod.git"},"bugs":{"url":"https://github.com/colinhacks/zod/issues"},"support":{"backing":{"npm-funding":true}},"scripts":{"clean":"git clean -xdf . -e node_modules","build":"zshy --project tsconfig.build.json","postbuild":"tsx ../../scripts/write-stub-package-jsons.ts && pnpm biome check --write .","test:watch":"pnpm vitest","test":"pnpm vitest run","prepublishOnly":"tsx ../../scripts/check-versions.ts"},"_lastModified":"2026-07-30T03:54:08.193Z"}
|
|
@@ -12,6 +12,7 @@ export declare const DEFAULT_MOBILE_UI_LAYOUT_UID = "mobile-layout-model";
|
|
|
12
12
|
export declare const DEFAULT_ADMIN_MULTI_PORTAL_UID = "__default_admin__";
|
|
13
13
|
export declare const DEFAULT_MOBILE_MULTI_PORTAL_UID = "__default_mobile__";
|
|
14
14
|
export type FlowSurfaceNavigationRequestRoles = readonly string[] | string;
|
|
15
|
+
type FlowSurfacePortalRouteScopeKind = 'layout' | 'portal';
|
|
15
16
|
export type FlowSurfaceResolvedMultiPortal = {
|
|
16
17
|
uid: string;
|
|
17
18
|
title: string;
|
|
@@ -22,6 +23,8 @@ export type FlowSurfaceResolvedMultiPortal = {
|
|
|
22
23
|
enabled: true;
|
|
23
24
|
layoutUid: string;
|
|
24
25
|
layoutType?: string;
|
|
26
|
+
portalType: 'no-code';
|
|
27
|
+
routeScopeKind: FlowSurfacePortalRouteScopeKind;
|
|
25
28
|
};
|
|
26
29
|
export type FlowSurfaceNavigationTarget = {
|
|
27
30
|
kind: 'layout' | 'portal';
|
|
@@ -56,6 +59,7 @@ export declare class FlowSurfaceNavigationTargetsService {
|
|
|
56
59
|
hasUiLayoutCapability(): boolean;
|
|
57
60
|
normalizePortalUid(value: unknown): string;
|
|
58
61
|
listNavigationTargets(currentRoles?: FlowSurfaceNavigationRequestRoles, transaction?: Transaction): Promise<FlowSurfaceNavigationTargetsResult>;
|
|
62
|
+
resolveDefaultPortal(options: Omit<PortalResolveOptions, 'path'>): Promise<FlowSurfaceResolvedMultiPortal>;
|
|
59
63
|
resolvePortal(portalUidValue: unknown, options: PortalResolveOptions): Promise<FlowSurfaceResolvedMultiPortal>;
|
|
60
64
|
readRoutePortalUids(routeId: unknown, transaction?: Transaction): Promise<string[]>;
|
|
61
65
|
assertRouteBelongsToPortal(actionName: string, routeId: unknown, portalUid: string, path: string, transaction?: Transaction): Promise<void>;
|
|
@@ -64,7 +68,10 @@ export declare class FlowSurfaceNavigationTargetsService {
|
|
|
64
68
|
transaction?: Transaction;
|
|
65
69
|
}): Promise<void>;
|
|
66
70
|
private listLayoutTargets;
|
|
71
|
+
private getEnabledPortalInventory;
|
|
67
72
|
private listAccessiblePortalTargets;
|
|
73
|
+
private comparePortalPriority;
|
|
74
|
+
private getPortalPriority;
|
|
68
75
|
private canAccessPortal;
|
|
69
76
|
private collectRouteTreeIds;
|
|
70
77
|
private removeDefaultLayoutRoutePermissions;
|
|
@@ -38,7 +38,6 @@ const DEFAULT_ADMIN_UI_LAYOUT_UID = "admin-layout-model";
|
|
|
38
38
|
const DEFAULT_MOBILE_UI_LAYOUT_UID = "mobile-layout-model";
|
|
39
39
|
const DEFAULT_ADMIN_MULTI_PORTAL_UID = "__default_admin__";
|
|
40
40
|
const DEFAULT_MOBILE_MULTI_PORTAL_UID = "__default_mobile__";
|
|
41
|
-
const RESERVED_MULTI_PORTAL_UIDS = /* @__PURE__ */ new Set([DEFAULT_ADMIN_MULTI_PORTAL_UID, DEFAULT_MOBILE_MULTI_PORTAL_UID]);
|
|
42
41
|
function readRecordField(record, field) {
|
|
43
42
|
if (!record || typeof record !== "object") {
|
|
44
43
|
return void 0;
|
|
@@ -60,6 +59,9 @@ function normalizeRoles(currentRoles) {
|
|
|
60
59
|
function normalizeRelationRouteId(routeId) {
|
|
61
60
|
return routeId === null || typeof routeId === "undefined" ? void 0 : routeId;
|
|
62
61
|
}
|
|
62
|
+
function isDefaultLayoutMultiPortalUid(uid) {
|
|
63
|
+
return uid === DEFAULT_ADMIN_MULTI_PORTAL_UID || uid === DEFAULT_MOBILE_MULTI_PORTAL_UID;
|
|
64
|
+
}
|
|
63
65
|
class FlowSurfaceNavigationTargetsService {
|
|
64
66
|
constructor(db) {
|
|
65
67
|
this.db = db;
|
|
@@ -85,9 +87,17 @@ class FlowSurfaceNavigationTargetsService {
|
|
|
85
87
|
return portalUid || void 0;
|
|
86
88
|
}
|
|
87
89
|
async listNavigationTargets(currentRoles, transaction) {
|
|
88
|
-
|
|
90
|
+
var _a;
|
|
91
|
+
let layoutTargets = await this.listLayoutTargets(transaction);
|
|
89
92
|
const multiPortal = this.hasMultiPortalCapability();
|
|
90
|
-
const
|
|
93
|
+
const portalInventory = multiPortal ? await this.getEnabledPortalInventory(currentRoles, transaction) : { accessible: [] };
|
|
94
|
+
const portalTargets = multiPortal ? await this.listAccessiblePortalTargets(portalInventory.accessible, transaction) : [];
|
|
95
|
+
if (multiPortal) {
|
|
96
|
+
layoutTargets = layoutTargets.map(({ default: _default, ...target }) => target);
|
|
97
|
+
if (portalInventory.accessible.length === 1 && ((_a = portalTargets[0]) == null ? void 0 : _a.uid) === readStringField(portalInventory.accessible[0], "uid")) {
|
|
98
|
+
portalTargets[0] = { ...portalTargets[0], default: true };
|
|
99
|
+
}
|
|
100
|
+
}
|
|
91
101
|
return {
|
|
92
102
|
version: "1",
|
|
93
103
|
capabilities: {
|
|
@@ -96,6 +106,60 @@ class FlowSurfaceNavigationTargetsService {
|
|
|
96
106
|
targets: [...layoutTargets, ...portalTargets]
|
|
97
107
|
};
|
|
98
108
|
}
|
|
109
|
+
async resolveDefaultPortal(options) {
|
|
110
|
+
if (!this.hasMultiPortalCapability()) {
|
|
111
|
+
(0, import_errors.throwBadRequest)(`flowSurfaces ${options.actionName} requires the Multi-portal capability`, {
|
|
112
|
+
ruleId: "navigation-portal-unsupported",
|
|
113
|
+
path: "navigation"
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
const inventory = await this.getEnabledPortalInventory(options.currentRoles, options.transaction);
|
|
117
|
+
if (!inventory.enabled.length) {
|
|
118
|
+
(0, import_errors.throwBadRequest)(`flowSurfaces ${options.actionName} requires an enabled portal`, {
|
|
119
|
+
ruleId: "navigation-portal-not-found",
|
|
120
|
+
path: "navigation",
|
|
121
|
+
details: {
|
|
122
|
+
uiBuilderAllowed: false,
|
|
123
|
+
adminLayoutFallbackAllowed: false,
|
|
124
|
+
agentInstruction: "Stop this Flow Surfaces write. Ask the user to create and enable a Portal before retrying; do not fall back to the Admin layout."
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
if (!inventory.accessible.length) {
|
|
129
|
+
(0, import_errors.throwForbidden)(
|
|
130
|
+
`flowSurfaces ${options.actionName} current roles cannot access an enabled portal`,
|
|
131
|
+
"FLOW_SURFACE_NAVIGATION_PORTAL_FORBIDDEN",
|
|
132
|
+
{
|
|
133
|
+
ruleId: "navigation-portal-forbidden",
|
|
134
|
+
path: "navigation"
|
|
135
|
+
}
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
if (inventory.accessible.length > 1) {
|
|
139
|
+
(0, import_errors.throwBadRequest)(`flowSurfaces ${options.actionName} requires an explicit portal selection`, {
|
|
140
|
+
ruleId: "navigation-portal-selection-required",
|
|
141
|
+
path: "navigation",
|
|
142
|
+
details: {
|
|
143
|
+
candidates: inventory.accessible.map((portal) => {
|
|
144
|
+
const uid = readStringField(portal, "uid");
|
|
145
|
+
return {
|
|
146
|
+
uid,
|
|
147
|
+
portalName: readStringField(portal, "portalName") || null,
|
|
148
|
+
title: readStringField(portal, "title") || uid,
|
|
149
|
+
portalType: readStringField(portal, "portalType") || null
|
|
150
|
+
};
|
|
151
|
+
}),
|
|
152
|
+
uiBuilderAllowed: false,
|
|
153
|
+
adminLayoutFallbackAllowed: false,
|
|
154
|
+
agentInstruction: "Stop this Flow Surfaces write and ask the user to select one candidate before retrying; do not choose by Portal type or fall back to the Admin layout."
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
return this.resolvePortal(readStringField(inventory.accessible[0], "uid"), {
|
|
159
|
+
...options,
|
|
160
|
+
path: "navigation"
|
|
161
|
+
});
|
|
162
|
+
}
|
|
99
163
|
async resolvePortal(portalUidValue, options) {
|
|
100
164
|
const portalUid = this.normalizePortalUid(portalUidValue);
|
|
101
165
|
if (!portalUid) {
|
|
@@ -111,19 +175,9 @@ class FlowSurfaceNavigationTargetsService {
|
|
|
111
175
|
details: { portalUid }
|
|
112
176
|
});
|
|
113
177
|
}
|
|
114
|
-
if (RESERVED_MULTI_PORTAL_UIDS.has(portalUid)) {
|
|
115
|
-
(0, import_errors.throwBadRequest)(
|
|
116
|
-
`flowSurfaces ${options.actionName} ${options.path} cannot use reserved default portal uid '${portalUid}'; use layoutUid semantics instead`,
|
|
117
|
-
{
|
|
118
|
-
ruleId: "navigation-portal-reserved",
|
|
119
|
-
path: options.path,
|
|
120
|
-
details: { portalUid }
|
|
121
|
-
}
|
|
122
|
-
);
|
|
123
|
-
}
|
|
124
178
|
const portal = await this.db.getRepository("multiPortals").findOne({
|
|
125
179
|
filter: { uid: portalUid },
|
|
126
|
-
fields: ["uid", "title", "icon", "
|
|
180
|
+
fields: ["uid", "title", "icon", "portalType", "portalName", "routePath", "authCheck", "enabled", "uiLayoutUid"],
|
|
127
181
|
transaction: options.transaction
|
|
128
182
|
});
|
|
129
183
|
if (!portal) {
|
|
@@ -133,6 +187,18 @@ class FlowSurfaceNavigationTargetsService {
|
|
|
133
187
|
details: { portalUid }
|
|
134
188
|
});
|
|
135
189
|
}
|
|
190
|
+
const routeScopeKind = isDefaultLayoutMultiPortalUid(portalUid) ? "layout" : "portal";
|
|
191
|
+
if (routeScopeKind === "portal" && !await this.canAccessPortal(portalUid, options.currentRoles, options.transaction)) {
|
|
192
|
+
(0, import_errors.throwForbidden)(
|
|
193
|
+
`flowSurfaces ${options.actionName} current roles cannot access portal '${portalUid}'`,
|
|
194
|
+
"FLOW_SURFACE_NAVIGATION_PORTAL_FORBIDDEN",
|
|
195
|
+
{
|
|
196
|
+
ruleId: "navigation-portal-forbidden",
|
|
197
|
+
path: options.path,
|
|
198
|
+
details: { portalUid }
|
|
199
|
+
}
|
|
200
|
+
);
|
|
201
|
+
}
|
|
136
202
|
if (readRecordField(portal, "enabled") !== true) {
|
|
137
203
|
(0, import_errors.throwBadRequest)(`flowSurfaces ${options.actionName} portal '${portalUid}' is disabled`, {
|
|
138
204
|
ruleId: "navigation-portal-disabled",
|
|
@@ -140,6 +206,33 @@ class FlowSurfaceNavigationTargetsService {
|
|
|
140
206
|
details: { portalUid }
|
|
141
207
|
});
|
|
142
208
|
}
|
|
209
|
+
const portalType = readStringField(portal, "portalType");
|
|
210
|
+
if (portalType !== "no-code") {
|
|
211
|
+
if (portalType === "ai") {
|
|
212
|
+
(0, import_errors.throwBadRequest)(
|
|
213
|
+
`flowSurfaces ${options.actionName} cannot use UI Builder or Flow Surfaces for AI Portal '${portalUid}'; stop and implement the request in the Portal source code`,
|
|
214
|
+
{
|
|
215
|
+
ruleId: "navigation-portal-type-unsupported",
|
|
216
|
+
path: options.path,
|
|
217
|
+
details: {
|
|
218
|
+
portalUid,
|
|
219
|
+
portalType,
|
|
220
|
+
portalName: readStringField(portal, "portalName") || null,
|
|
221
|
+
expectedPortalType: "no-code",
|
|
222
|
+
uiBuilderAllowed: false,
|
|
223
|
+
adminLayoutFallbackAllowed: false,
|
|
224
|
+
implementationPath: "ai-portal-source",
|
|
225
|
+
agentInstruction: "Stop this Flow Surfaces write. Run nb portal info <portalName> -j to resolve localPath, then implement the request in that AI Portal source code. Do not retry with an Admin layout."
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
(0, import_errors.throwBadRequest)(`flowSurfaces ${options.actionName} portal '${portalUid}' does not support no-code routes`, {
|
|
231
|
+
ruleId: "navigation-portal-type-unsupported",
|
|
232
|
+
path: options.path,
|
|
233
|
+
details: { portalUid, portalType: portalType || null }
|
|
234
|
+
});
|
|
235
|
+
}
|
|
143
236
|
const layoutUid = readStringField(portal, "uiLayoutUid");
|
|
144
237
|
if (!layoutUid || !this.db.getCollection("uiLayouts")) {
|
|
145
238
|
(0, import_errors.throwBadRequest)(`flowSurfaces ${options.actionName} portal '${portalUid}' has no available backing UI layout`, {
|
|
@@ -173,27 +266,18 @@ class FlowSurfaceNavigationTargetsService {
|
|
|
173
266
|
}
|
|
174
267
|
);
|
|
175
268
|
}
|
|
176
|
-
if (!await this.canAccessPortal(portalUid, options.currentRoles, options.transaction)) {
|
|
177
|
-
(0, import_errors.throwForbidden)(
|
|
178
|
-
`flowSurfaces ${options.actionName} current roles cannot access portal '${portalUid}'`,
|
|
179
|
-
"FLOW_SURFACE_NAVIGATION_PORTAL_FORBIDDEN",
|
|
180
|
-
{
|
|
181
|
-
ruleId: "navigation-portal-forbidden",
|
|
182
|
-
path: options.path,
|
|
183
|
-
details: { portalUid }
|
|
184
|
-
}
|
|
185
|
-
);
|
|
186
|
-
}
|
|
187
269
|
return {
|
|
188
270
|
uid: portalUid,
|
|
189
271
|
title: readStringField(portal, "title") || portalUid,
|
|
190
272
|
icon: readStringField(portal, "icon") || null,
|
|
191
|
-
routeName: readStringField(portal, "
|
|
273
|
+
routeName: readStringField(portal, "portalName"),
|
|
192
274
|
routePath: readStringField(portal, "routePath"),
|
|
193
275
|
authCheck: readRecordField(portal, "authCheck") === true,
|
|
194
276
|
enabled: true,
|
|
195
277
|
layoutUid,
|
|
196
|
-
layoutType: readStringField(layout, "layoutType")
|
|
278
|
+
layoutType: readStringField(layout, "layoutType"),
|
|
279
|
+
portalType,
|
|
280
|
+
routeScopeKind
|
|
197
281
|
};
|
|
198
282
|
}
|
|
199
283
|
async readRoutePortalUids(routeId, transaction) {
|
|
@@ -293,43 +377,48 @@ class FlowSurfaceNavigationTargetsService {
|
|
|
293
377
|
return left.uid.localeCompare(right.uid);
|
|
294
378
|
});
|
|
295
379
|
}
|
|
296
|
-
async
|
|
380
|
+
async getEnabledPortalInventory(currentRoles, transaction) {
|
|
381
|
+
const enabled = await this.db.getRepository("multiPortals").find({
|
|
382
|
+
filter: { enabled: true },
|
|
383
|
+
fields: ["uid", "title", "icon", "portalType", "portalName", "routePath", "authCheck", "enabled", "uiLayoutUid"],
|
|
384
|
+
sort: ["uid"],
|
|
385
|
+
transaction
|
|
386
|
+
});
|
|
387
|
+
enabled.sort(
|
|
388
|
+
(left, right) => String(readStringField(left, "uid") || "").localeCompare(String(readStringField(right, "uid") || ""))
|
|
389
|
+
);
|
|
297
390
|
const roles = normalizeRoles(currentRoles);
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
return [];
|
|
302
|
-
}
|
|
391
|
+
const isRoot = roles.includes("root");
|
|
392
|
+
const accessiblePortalUids = /* @__PURE__ */ new Set();
|
|
393
|
+
if (!isRoot && roles.length && this.db.getCollection("rolesMultiPortals")) {
|
|
303
394
|
const grants = await this.db.getRepository("rolesMultiPortals").find({
|
|
304
|
-
fields: ["multiPortalUid"],
|
|
305
395
|
filter: { roleName: roles },
|
|
396
|
+
fields: ["multiPortalUid"],
|
|
306
397
|
transaction
|
|
307
398
|
});
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
if (!accessiblePortalUids.length) {
|
|
314
|
-
return [];
|
|
399
|
+
for (const grant of grants) {
|
|
400
|
+
const portalUid = readStringField(grant, "multiPortalUid");
|
|
401
|
+
if (portalUid) {
|
|
402
|
+
accessiblePortalUids.add(portalUid);
|
|
403
|
+
}
|
|
315
404
|
}
|
|
316
405
|
}
|
|
317
|
-
const
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
"uid.$notIn": Array.from(RESERVED_MULTI_PORTAL_UIDS)
|
|
321
|
-
};
|
|
322
|
-
const portals = await this.db.getRepository("multiPortals").find({
|
|
323
|
-
filter,
|
|
324
|
-
fields: ["uid", "title", "icon", "routeName", "routePath", "authCheck", "enabled", "uiLayoutUid"],
|
|
325
|
-
sort: ["uid"],
|
|
326
|
-
transaction
|
|
406
|
+
const accessible = enabled.filter((portal) => {
|
|
407
|
+
const portalUid = readStringField(portal, "uid");
|
|
408
|
+
return !!portalUid && (isRoot || isDefaultLayoutMultiPortalUid(portalUid) || accessiblePortalUids.has(portalUid));
|
|
327
409
|
});
|
|
410
|
+
return { enabled, accessible };
|
|
411
|
+
}
|
|
412
|
+
async listAccessiblePortalTargets(portals, transaction) {
|
|
328
413
|
const targets = [];
|
|
329
414
|
for (const portal of portals) {
|
|
330
415
|
const portalUid = readStringField(portal, "uid");
|
|
331
416
|
const layoutUid = readStringField(portal, "uiLayoutUid");
|
|
332
|
-
|
|
417
|
+
const portalType = readStringField(portal, "portalType");
|
|
418
|
+
if (!portalUid || portalType !== "no-code") {
|
|
419
|
+
continue;
|
|
420
|
+
}
|
|
421
|
+
if (!layoutUid || !this.db.getCollection("uiLayouts")) {
|
|
333
422
|
continue;
|
|
334
423
|
}
|
|
335
424
|
const layout = await this.db.getRepository("uiLayouts").findOne({
|
|
@@ -348,12 +437,30 @@ class FlowSurfaceNavigationTargetsService {
|
|
|
348
437
|
icon: readStringField(portal, "icon") || null,
|
|
349
438
|
layoutUid,
|
|
350
439
|
layoutType: readStringField(layout, "layoutType"),
|
|
351
|
-
routeName: readStringField(portal, "
|
|
440
|
+
routeName: readStringField(portal, "portalName"),
|
|
352
441
|
routePath: readStringField(portal, "routePath"),
|
|
353
442
|
authCheck: readRecordField(portal, "authCheck") === true
|
|
354
443
|
});
|
|
355
444
|
}
|
|
356
|
-
return targets;
|
|
445
|
+
return targets.sort((left, right) => this.comparePortalPriority(left, right));
|
|
446
|
+
}
|
|
447
|
+
comparePortalPriority(left, right) {
|
|
448
|
+
const priorityDelta = this.getPortalPriority(left) - this.getPortalPriority(right);
|
|
449
|
+
if (priorityDelta) {
|
|
450
|
+
return priorityDelta;
|
|
451
|
+
}
|
|
452
|
+
return String(readStringField(left, "uid") || "").localeCompare(String(readStringField(right, "uid") || ""));
|
|
453
|
+
}
|
|
454
|
+
getPortalPriority(portal) {
|
|
455
|
+
const uiLayout = readRecordField(portal, "uiLayout");
|
|
456
|
+
const layoutType = readStringField(portal, "layoutType") || readStringField(uiLayout, "layoutType");
|
|
457
|
+
if (readStringField(portal, "uid") === DEFAULT_ADMIN_MULTI_PORTAL_UID) {
|
|
458
|
+
return 0;
|
|
459
|
+
}
|
|
460
|
+
if (layoutType !== "mobile") {
|
|
461
|
+
return 1;
|
|
462
|
+
}
|
|
463
|
+
return 2;
|
|
357
464
|
}
|
|
358
465
|
async canAccessPortal(portalUid, currentRoles, transaction) {
|
|
359
466
|
const roles = normalizeRoles(currentRoles);
|
|
@@ -96,6 +96,12 @@ export declare class FlowSurfacesService {
|
|
|
96
96
|
private resolveDesktopRouteLayoutUidFilter;
|
|
97
97
|
private assertNavigationTargetIsExclusive;
|
|
98
98
|
private readDesktopRouteScope;
|
|
99
|
+
private buildDesktopRouteScopeForPortal;
|
|
100
|
+
private assertAdminLayoutCreateScope;
|
|
101
|
+
private getPortalRouteFilterUid;
|
|
102
|
+
private assertRouteBelongsToResolvedPortal;
|
|
103
|
+
private resolveDefaultDesktopRouteScope;
|
|
104
|
+
private resolveDesktopRouteMatchScope;
|
|
99
105
|
private resolveRequestedDesktopRouteScope;
|
|
100
106
|
private resolveExistingDesktopRouteScope;
|
|
101
107
|
private attachDesktopRouteScopeToRouteAndChildren;
|
|
@@ -227,6 +233,7 @@ export declare class FlowSurfacesService {
|
|
|
227
233
|
private dispatchPlanOnlyAction;
|
|
228
234
|
private resolveApplyBlueprintReplaceTargetInfo;
|
|
229
235
|
private resolveApplyBlueprintCreateNavigationGroup;
|
|
236
|
+
private resolveApplyBlueprintDefaultNavigationTarget;
|
|
230
237
|
private normalizeApplyBlueprintCreateMobileNavigation;
|
|
231
238
|
private assertApplyBlueprintCreateNavigationTarget;
|
|
232
239
|
private resolveApplyBlueprintCreatePageIdentity;
|
|
@@ -1204,6 +1204,83 @@ class FlowSurfacesService {
|
|
|
1204
1204
|
layoutUids: await this.readDesktopRouteUiLayoutUids(routeId, transaction)
|
|
1205
1205
|
};
|
|
1206
1206
|
}
|
|
1207
|
+
buildDesktopRouteScopeForPortal(portal) {
|
|
1208
|
+
return {
|
|
1209
|
+
portalUids: portal.routeScopeKind === "portal" ? [portal.uid] : [],
|
|
1210
|
+
layoutUids: portal.routeScopeKind === "layout" ? [portal.layoutUid] : [],
|
|
1211
|
+
selectedPortal: portal,
|
|
1212
|
+
layoutType: portal.layoutType
|
|
1213
|
+
};
|
|
1214
|
+
}
|
|
1215
|
+
assertAdminLayoutCreateScope(actionName, scope, path) {
|
|
1216
|
+
if (!this.navigationTargets.hasMultiPortalCapability() || scope.selectedPortal || !scope.layoutUids.includes(import_navigation_targets.DEFAULT_ADMIN_UI_LAYOUT_UID)) {
|
|
1217
|
+
return;
|
|
1218
|
+
}
|
|
1219
|
+
(0, import_errors.throwBadRequest)(`flowSurfaces ${actionName} cannot create navigation directly in the Admin layout`, {
|
|
1220
|
+
ruleId: "navigation-admin-layout-not-portal-target",
|
|
1221
|
+
path,
|
|
1222
|
+
details: {
|
|
1223
|
+
layoutUid: import_navigation_targets.DEFAULT_ADMIN_UI_LAYOUT_UID,
|
|
1224
|
+
uiBuilderAllowed: false,
|
|
1225
|
+
adminLayoutFallbackAllowed: false,
|
|
1226
|
+
agentInstruction: "Resolve the target Portal first. For a no-code Portal, pass portalUid (navigation.portalUid in applyBlueprint); for an AI Portal, stop this Flow Surfaces write and implement the request in the Portal source code."
|
|
1227
|
+
}
|
|
1228
|
+
});
|
|
1229
|
+
}
|
|
1230
|
+
getPortalRouteFilterUid(portal) {
|
|
1231
|
+
return (portal == null ? void 0 : portal.routeScopeKind) === "portal" ? portal.uid : void 0;
|
|
1232
|
+
}
|
|
1233
|
+
async assertRouteBelongsToResolvedPortal(actionName, route, portal, path, transaction) {
|
|
1234
|
+
if (portal.routeScopeKind === "layout") {
|
|
1235
|
+
await this.assertDesktopRouteBelongsToUiLayout(actionName, route, portal.layoutUid, path, transaction);
|
|
1236
|
+
return;
|
|
1237
|
+
}
|
|
1238
|
+
await this.navigationTargets.assertRouteBelongsToPortal(
|
|
1239
|
+
actionName,
|
|
1240
|
+
this.readRouteField(route, "id"),
|
|
1241
|
+
portal.uid,
|
|
1242
|
+
path,
|
|
1243
|
+
transaction
|
|
1244
|
+
);
|
|
1245
|
+
}
|
|
1246
|
+
async resolveDefaultDesktopRouteScope(actionName, options = {}) {
|
|
1247
|
+
if (this.navigationTargets.hasMultiPortalCapability()) {
|
|
1248
|
+
const portal = await this.navigationTargets.resolveDefaultPortal({
|
|
1249
|
+
actionName,
|
|
1250
|
+
currentRoles: options.currentRoles,
|
|
1251
|
+
transaction: options.transaction
|
|
1252
|
+
});
|
|
1253
|
+
return this.buildDesktopRouteScopeForPortal(portal);
|
|
1254
|
+
}
|
|
1255
|
+
const layoutUids = await this.resolveDefaultDesktopRouteUiLayoutUids(options.transaction);
|
|
1256
|
+
return {
|
|
1257
|
+
portalUids: [],
|
|
1258
|
+
layoutUids,
|
|
1259
|
+
layoutType: layoutUids.length === 1 ? await this.getDesktopRouteUiLayoutTypeByUid(layoutUids[0], options.transaction) : void 0
|
|
1260
|
+
};
|
|
1261
|
+
}
|
|
1262
|
+
async resolveDesktopRouteMatchScope(layoutUid, portalUid, actionName, options = {}) {
|
|
1263
|
+
if (portalUid) {
|
|
1264
|
+
const portal = await this.navigationTargets.resolvePortal(portalUid, {
|
|
1265
|
+
actionName,
|
|
1266
|
+
path: "values.navigation.portalUid",
|
|
1267
|
+
currentRoles: options.currentRoles,
|
|
1268
|
+
transaction: options.transaction
|
|
1269
|
+
});
|
|
1270
|
+
return {
|
|
1271
|
+
layoutUids: portal.routeScopeKind === "layout" ? [portal.layoutUid] : void 0,
|
|
1272
|
+
portalUid: this.getPortalRouteFilterUid(portal)
|
|
1273
|
+
};
|
|
1274
|
+
}
|
|
1275
|
+
if (this.normalizeDesktopRouteLayoutUidFilter(layoutUid).length) {
|
|
1276
|
+
return { layoutUids: layoutUid, portalUid: void 0 };
|
|
1277
|
+
}
|
|
1278
|
+
const defaultScope = await this.resolveDefaultDesktopRouteScope(actionName, options);
|
|
1279
|
+
return {
|
|
1280
|
+
layoutUids: defaultScope.layoutUids.length ? defaultScope.layoutUids : void 0,
|
|
1281
|
+
portalUid: this.getPortalRouteFilterUid(defaultScope.selectedPortal)
|
|
1282
|
+
};
|
|
1283
|
+
}
|
|
1207
1284
|
async resolveRequestedDesktopRouteScope(values, parentRoute, actionName, options = {}) {
|
|
1208
1285
|
const layoutUid = this.normalizeExplicitDesktopRouteLayoutUid(values.layoutUid);
|
|
1209
1286
|
const portalUid = this.navigationTargets.normalizePortalUid(values.portalUid);
|
|
@@ -1219,22 +1296,19 @@ class FlowSurfacesService {
|
|
|
1219
1296
|
transaction: options.transaction
|
|
1220
1297
|
});
|
|
1221
1298
|
if (parentRoute) {
|
|
1222
|
-
await this.
|
|
1299
|
+
await this.assertRouteBelongsToResolvedPortal(
|
|
1223
1300
|
actionName,
|
|
1224
|
-
|
|
1225
|
-
portal
|
|
1301
|
+
parentRoute,
|
|
1302
|
+
portal,
|
|
1226
1303
|
"values.parentMenuRouteId",
|
|
1227
1304
|
options.transaction
|
|
1228
1305
|
);
|
|
1229
1306
|
}
|
|
1230
|
-
return
|
|
1231
|
-
portalUids: [portal.uid],
|
|
1232
|
-
layoutUids: [],
|
|
1233
|
-
selectedPortal: portal,
|
|
1234
|
-
layoutType: portal.layoutType
|
|
1235
|
-
};
|
|
1307
|
+
return this.buildDesktopRouteScopeForPortal(portal);
|
|
1236
1308
|
}
|
|
1237
1309
|
if (layoutUid) {
|
|
1310
|
+
const scope2 = { portalUids: [], layoutUids: [layoutUid] };
|
|
1311
|
+
this.assertAdminLayoutCreateScope(actionName, scope2, "values.layoutUid");
|
|
1238
1312
|
await this.assertEnabledDesktopRouteUiLayoutUid(layoutUid, actionName, "values.layoutUid", options.transaction);
|
|
1239
1313
|
if (parentRoute) {
|
|
1240
1314
|
await this.assertDesktopRouteBelongsToUiLayout(
|
|
@@ -1246,8 +1320,7 @@ class FlowSurfacesService {
|
|
|
1246
1320
|
);
|
|
1247
1321
|
}
|
|
1248
1322
|
return {
|
|
1249
|
-
|
|
1250
|
-
layoutUids: [layoutUid],
|
|
1323
|
+
...scope2,
|
|
1251
1324
|
layoutType: await this.getDesktopRouteUiLayoutTypeByUid(layoutUid, options.transaction)
|
|
1252
1325
|
};
|
|
1253
1326
|
}
|
|
@@ -1280,10 +1353,17 @@ class FlowSurfacesService {
|
|
|
1280
1353
|
};
|
|
1281
1354
|
}
|
|
1282
1355
|
}
|
|
1356
|
+
if (!parentRoute) {
|
|
1357
|
+
return this.resolveDefaultDesktopRouteScope(actionName, options);
|
|
1358
|
+
}
|
|
1283
1359
|
const layoutUids = await this.resolveInheritedDesktopRouteUiLayoutUids(parentRoute, options.transaction);
|
|
1284
|
-
|
|
1360
|
+
const scope = {
|
|
1285
1361
|
portalUids: [],
|
|
1286
|
-
layoutUids
|
|
1362
|
+
layoutUids
|
|
1363
|
+
};
|
|
1364
|
+
this.assertAdminLayoutCreateScope(actionName, scope, "values.parentMenuRouteId");
|
|
1365
|
+
return {
|
|
1366
|
+
...scope,
|
|
1287
1367
|
layoutType: layoutUids.length === 1 ? await this.getDesktopRouteUiLayoutTypeByUid(layoutUids[0], options.transaction) : void 0
|
|
1288
1368
|
};
|
|
1289
1369
|
}
|
|
@@ -1302,20 +1382,18 @@ class FlowSurfacesService {
|
|
|
1302
1382
|
currentRoles: options.currentRoles,
|
|
1303
1383
|
transaction: options.transaction
|
|
1304
1384
|
});
|
|
1305
|
-
await this.
|
|
1385
|
+
await this.assertRouteBelongsToResolvedPortal(
|
|
1306
1386
|
actionName,
|
|
1307
|
-
|
|
1308
|
-
portal
|
|
1387
|
+
route,
|
|
1388
|
+
portal,
|
|
1309
1389
|
"values.menuRouteId",
|
|
1310
1390
|
options.transaction
|
|
1311
1391
|
);
|
|
1312
|
-
return
|
|
1313
|
-
...routeScope,
|
|
1314
|
-
selectedPortal: portal,
|
|
1315
|
-
layoutType: portal.layoutType
|
|
1316
|
-
};
|
|
1392
|
+
return this.buildDesktopRouteScopeForPortal(portal);
|
|
1317
1393
|
}
|
|
1318
1394
|
if (layoutUid) {
|
|
1395
|
+
const scope2 = { ...routeScope, layoutUids: [layoutUid] };
|
|
1396
|
+
this.assertAdminLayoutCreateScope(actionName, scope2, "values.layoutUid");
|
|
1319
1397
|
await this.assertEnabledDesktopRouteUiLayoutUid(layoutUid, actionName, "values.layoutUid", options.transaction);
|
|
1320
1398
|
await this.assertDesktopRouteBelongsToUiLayout(
|
|
1321
1399
|
actionName,
|
|
@@ -1325,7 +1403,7 @@ class FlowSurfacesService {
|
|
|
1325
1403
|
options.transaction
|
|
1326
1404
|
);
|
|
1327
1405
|
return {
|
|
1328
|
-
...
|
|
1406
|
+
...scope2,
|
|
1329
1407
|
layoutType: await this.getDesktopRouteUiLayoutTypeByUid(layoutUid, options.transaction)
|
|
1330
1408
|
};
|
|
1331
1409
|
}
|
|
@@ -1355,10 +1433,22 @@ class FlowSurfacesService {
|
|
|
1355
1433
|
layoutType: portal.layoutType
|
|
1356
1434
|
};
|
|
1357
1435
|
}
|
|
1358
|
-
|
|
1359
|
-
|
|
1436
|
+
let layoutUids = routeScope.layoutUids;
|
|
1437
|
+
if (!layoutUids.length) {
|
|
1438
|
+
const parentRouteId = this.readRouteField(route, "parentId");
|
|
1439
|
+
const parentRoute = import_lodash.default.isNil(parentRouteId) ? null : await this.findMenuRouteById(parentRouteId, options.transaction);
|
|
1440
|
+
layoutUids = await this.resolveInheritedDesktopRouteUiLayoutUids(parentRoute, options.transaction);
|
|
1441
|
+
}
|
|
1442
|
+
const scope = {
|
|
1360
1443
|
portalUids: [],
|
|
1361
|
-
layoutUids
|
|
1444
|
+
layoutUids
|
|
1445
|
+
};
|
|
1446
|
+
this.assertAdminLayoutCreateScope(actionName, scope, "values.menuRouteId");
|
|
1447
|
+
if (!routeScope.layoutUids.length) {
|
|
1448
|
+
await this.setDesktopRouteUiLayouts(this.readRouteField(route, "id"), layoutUids, options.transaction);
|
|
1449
|
+
}
|
|
1450
|
+
return {
|
|
1451
|
+
...scope,
|
|
1362
1452
|
layoutType: layoutUids.length === 1 ? await this.getDesktopRouteUiLayoutTypeByUid(layoutUids[0], options.transaction) : void 0
|
|
1363
1453
|
};
|
|
1364
1454
|
}
|
|
@@ -1518,7 +1608,6 @@ class FlowSurfacesService {
|
|
|
1518
1608
|
}
|
|
1519
1609
|
}
|
|
1520
1610
|
async createFlowMenuGroup(values, options = {}) {
|
|
1521
|
-
var _a;
|
|
1522
1611
|
const parentRoute = await this.assertMenuParentIsGroup(values.parentMenuRouteId, options.transaction);
|
|
1523
1612
|
const parentId = this.readRouteField(parentRoute, "id") ?? null;
|
|
1524
1613
|
const routeScope = await this.resolveRequestedDesktopRouteScope(values, parentRoute, "createMenu", options);
|
|
@@ -1528,7 +1617,7 @@ class FlowSurfacesService {
|
|
|
1528
1617
|
title,
|
|
1529
1618
|
options.transaction,
|
|
1530
1619
|
routeScope.layoutUids,
|
|
1531
|
-
(
|
|
1620
|
+
this.getPortalRouteFilterUid(routeScope.selectedPortal)
|
|
1532
1621
|
);
|
|
1533
1622
|
if (existingGroups.length === 1) {
|
|
1534
1623
|
return this.buildMenuResult(existingGroups[0]);
|
|
@@ -3569,7 +3658,7 @@ class FlowSurfacesService {
|
|
|
3569
3658
|
tabs
|
|
3570
3659
|
};
|
|
3571
3660
|
}
|
|
3572
|
-
async resolveApplyBlueprintCreateNavigationGroup(document,
|
|
3661
|
+
async resolveApplyBlueprintCreateNavigationGroup(document, options = {}) {
|
|
3573
3662
|
var _a;
|
|
3574
3663
|
if (document.mode !== "create" || !import_lodash.default.isPlainObject((_a = document.navigation) == null ? void 0 : _a.group)) {
|
|
3575
3664
|
return document;
|
|
@@ -3583,12 +3672,13 @@ class FlowSurfacesService {
|
|
|
3583
3672
|
}
|
|
3584
3673
|
const layoutUid = this.normalizeExplicitDesktopRouteLayoutUid(document.navigation.layoutUid);
|
|
3585
3674
|
const portalUid = this.navigationTargets.normalizePortalUid(document.navigation.portalUid);
|
|
3675
|
+
const matchScope = await this.resolveDesktopRouteMatchScope(layoutUid, portalUid, "applyBlueprint", options);
|
|
3586
3676
|
const matchedRoutes = await this.findMenuGroupRoutesByParentIdAndTitle(
|
|
3587
3677
|
null,
|
|
3588
3678
|
groupTitle,
|
|
3589
|
-
transaction,
|
|
3590
|
-
|
|
3591
|
-
portalUid
|
|
3679
|
+
options.transaction,
|
|
3680
|
+
matchScope.layoutUids,
|
|
3681
|
+
matchScope.portalUid
|
|
3592
3682
|
);
|
|
3593
3683
|
if (!matchedRoutes.length) {
|
|
3594
3684
|
return document;
|
|
@@ -3614,6 +3704,24 @@ class FlowSurfacesService {
|
|
|
3614
3704
|
}
|
|
3615
3705
|
};
|
|
3616
3706
|
}
|
|
3707
|
+
async resolveApplyBlueprintDefaultNavigationTarget(document, options = {}) {
|
|
3708
|
+
var _a, _b, _c, _d;
|
|
3709
|
+
if (document.mode !== "create" || ((_a = document.navigation) == null ? void 0 : _a.layoutUid) || ((_b = document.navigation) == null ? void 0 : _b.portalUid) || !import_lodash.default.isUndefined((_d = (_c = document.navigation) == null ? void 0 : _c.group) == null ? void 0 : _d.routeId) || !this.navigationTargets.hasMultiPortalCapability()) {
|
|
3710
|
+
return document;
|
|
3711
|
+
}
|
|
3712
|
+
const portal = await this.navigationTargets.resolveDefaultPortal({
|
|
3713
|
+
actionName: "applyBlueprint",
|
|
3714
|
+
currentRoles: options.currentRoles,
|
|
3715
|
+
transaction: options.transaction
|
|
3716
|
+
});
|
|
3717
|
+
return {
|
|
3718
|
+
...document,
|
|
3719
|
+
navigation: {
|
|
3720
|
+
...document.navigation,
|
|
3721
|
+
portalUid: portal.uid
|
|
3722
|
+
}
|
|
3723
|
+
};
|
|
3724
|
+
}
|
|
3617
3725
|
async normalizeApplyBlueprintCreateMobileNavigation(document, options = {}) {
|
|
3618
3726
|
var _a, _b;
|
|
3619
3727
|
if (document.mode !== "create") {
|
|
@@ -3669,15 +3777,20 @@ class FlowSurfacesService {
|
|
|
3669
3777
|
return;
|
|
3670
3778
|
}
|
|
3671
3779
|
const groupRoute2 = await this.assertMenuParentIsGroup(groupRouteId2, options.transaction);
|
|
3672
|
-
await this.
|
|
3780
|
+
await this.assertRouteBelongsToResolvedPortal(
|
|
3673
3781
|
"applyBlueprint",
|
|
3674
|
-
|
|
3675
|
-
portal
|
|
3782
|
+
groupRoute2,
|
|
3783
|
+
portal,
|
|
3676
3784
|
"values.navigation.group.routeId",
|
|
3677
3785
|
options.transaction
|
|
3678
3786
|
);
|
|
3679
3787
|
return;
|
|
3680
3788
|
}
|
|
3789
|
+
this.assertAdminLayoutCreateScope(
|
|
3790
|
+
"applyBlueprint",
|
|
3791
|
+
{ portalUids: [], layoutUids: [layoutUid] },
|
|
3792
|
+
"values.navigation.layoutUid"
|
|
3793
|
+
);
|
|
3681
3794
|
await this.assertEnabledDesktopRouteUiLayoutUid(
|
|
3682
3795
|
layoutUid,
|
|
3683
3796
|
"applyBlueprint",
|
|
@@ -3700,7 +3813,7 @@ class FlowSurfacesService {
|
|
|
3700
3813
|
options.transaction
|
|
3701
3814
|
);
|
|
3702
3815
|
}
|
|
3703
|
-
async resolveApplyBlueprintCreatePageIdentity(document,
|
|
3816
|
+
async resolveApplyBlueprintCreatePageIdentity(document, options = {}) {
|
|
3704
3817
|
var _a, _b, _c, _d, _e, _f, _g;
|
|
3705
3818
|
if (document.mode !== "create") {
|
|
3706
3819
|
return document;
|
|
@@ -3713,12 +3826,10 @@ class FlowSurfacesService {
|
|
|
3713
3826
|
if (!pageTitle || !hasGroupRouteId && !layoutUid && !portalUid) {
|
|
3714
3827
|
return document;
|
|
3715
3828
|
}
|
|
3716
|
-
const groupRoute = hasGroupRouteId ? await this.findMenuRouteById(groupRouteId, transaction) : null;
|
|
3829
|
+
const groupRoute = hasGroupRouteId ? await this.findMenuRouteById(groupRouteId, options.transaction) : null;
|
|
3830
|
+
const groupScope = groupRoute && !portalUid ? await this.readDesktopRouteScope(groupRoute, options.transaction) : void 0;
|
|
3717
3831
|
if (!portalUid && groupRoute) {
|
|
3718
|
-
const groupPortalUids =
|
|
3719
|
-
this.readRouteField(groupRoute, "id"),
|
|
3720
|
-
transaction
|
|
3721
|
-
);
|
|
3832
|
+
const groupPortalUids = (groupScope == null ? void 0 : groupScope.portalUids) || [];
|
|
3722
3833
|
if (groupPortalUids.length > 1) {
|
|
3723
3834
|
(0, import_errors.throwBadRequest)(
|
|
3724
3835
|
`flowSurfaces applyBlueprint navigation.group.routeId '${groupRouteId}' belongs to multiple portals; pass navigation.portalUid explicitly`,
|
|
@@ -3734,13 +3845,23 @@ class FlowSurfacesService {
|
|
|
3734
3845
|
}
|
|
3735
3846
|
portalUid = groupPortalUids[0];
|
|
3736
3847
|
}
|
|
3737
|
-
const
|
|
3848
|
+
const portalMatchScope = portalUid ? await this.resolveDesktopRouteMatchScope(layoutUid, portalUid, "applyBlueprint", options) : void 0;
|
|
3849
|
+
let layoutUidFilter = (portalMatchScope == null ? void 0 : portalMatchScope.layoutUids) || layoutUid;
|
|
3850
|
+
if (!portalMatchScope && !layoutUid && groupRoute) {
|
|
3851
|
+
layoutUidFilter = (groupScope == null ? void 0 : groupScope.layoutUids.length) ? groupScope.layoutUids : await this.resolveInheritedDesktopRouteUiLayoutUids(groupRoute, options.transaction);
|
|
3852
|
+
this.assertAdminLayoutCreateScope(
|
|
3853
|
+
"applyBlueprint",
|
|
3854
|
+
{ portalUids: [], layoutUids: this.normalizeDesktopRouteLayoutUidFilter(layoutUidFilter) },
|
|
3855
|
+
"values.navigation.group.routeId"
|
|
3856
|
+
);
|
|
3857
|
+
}
|
|
3858
|
+
const portalUidFilter = portalMatchScope == null ? void 0 : portalMatchScope.portalUid;
|
|
3738
3859
|
const matchedPages = await this.findFlowPageRoutesByParentIdAndTitle(
|
|
3739
3860
|
hasGroupRouteId ? groupRouteId : null,
|
|
3740
3861
|
pageTitle,
|
|
3741
|
-
transaction,
|
|
3862
|
+
options.transaction,
|
|
3742
3863
|
layoutUidFilter,
|
|
3743
|
-
|
|
3864
|
+
portalUidFilter
|
|
3744
3865
|
);
|
|
3745
3866
|
if (!matchedPages.length) {
|
|
3746
3867
|
return document;
|
|
@@ -3769,14 +3890,15 @@ class FlowSurfacesService {
|
|
|
3769
3890
|
}
|
|
3770
3891
|
async prepareApplyBlueprintRequest(values, options = {}, createdKanbanSortFields) {
|
|
3771
3892
|
var _a;
|
|
3772
|
-
const
|
|
3893
|
+
const parsedDocument = (0, import_blueprint.prepareFlowSurfaceApplyBlueprintDocument)(values);
|
|
3894
|
+
const initialDocument = await this.resolveApplyBlueprintDefaultNavigationTarget(parsedDocument, options);
|
|
3773
3895
|
await this.assertApplyBlueprintCreateNavigationTarget(initialDocument, options);
|
|
3774
3896
|
const mobileNormalizedDocument = await this.normalizeApplyBlueprintCreateMobileNavigation(initialDocument, options);
|
|
3775
3897
|
const groupResolvedDocument = await this.resolveApplyBlueprintCreateNavigationGroup(
|
|
3776
3898
|
mobileNormalizedDocument,
|
|
3777
|
-
options
|
|
3899
|
+
options
|
|
3778
3900
|
);
|
|
3779
|
-
const document = await this.resolveApplyBlueprintCreatePageIdentity(groupResolvedDocument, options
|
|
3901
|
+
const document = await this.resolveApplyBlueprintCreatePageIdentity(groupResolvedDocument, options);
|
|
3780
3902
|
await this.prepareApplyBlueprintKanbanBlocks(document, options.transaction, createdKanbanSortFields);
|
|
3781
3903
|
const replaceTarget = document.mode === "replace" && document.target ? await this.resolveApplyBlueprintReplaceTargetInfo(document.target.pageSchemaUid, options.transaction) : void 0;
|
|
3782
3904
|
if (document.mode === "replace" && document.tabs.length > 1 && typeof ((_a = document.page) == null ? void 0 : _a.enableTabs) === "undefined" && (replaceTarget == null ? void 0 : replaceTarget.pageEnableTabs) === false) {
|
|
@@ -4445,7 +4567,18 @@ class FlowSurfacesService {
|
|
|
4445
4567
|
await (0, import_authoring_validation.assertFlowSurfaceAuthoringPayload)("applyBlueprint", values, {
|
|
4446
4568
|
transaction: options.transaction,
|
|
4447
4569
|
enabledPackages,
|
|
4448
|
-
findMenuGroupRoutesByTitle: (title, transaction, targetLayoutUid, targetPortalUid) =>
|
|
4570
|
+
findMenuGroupRoutesByTitle: async (title, transaction, targetLayoutUid, targetPortalUid) => {
|
|
4571
|
+
const matchScope = await this.resolveDesktopRouteMatchScope(
|
|
4572
|
+
targetLayoutUid,
|
|
4573
|
+
targetPortalUid,
|
|
4574
|
+
"applyBlueprint",
|
|
4575
|
+
{
|
|
4576
|
+
transaction,
|
|
4577
|
+
currentRoles: options.currentRoles
|
|
4578
|
+
}
|
|
4579
|
+
);
|
|
4580
|
+
return this.findMenuGroupRoutesByTitle(title, transaction, matchScope.layoutUids, matchScope.portalUid);
|
|
4581
|
+
},
|
|
4449
4582
|
getUiLayoutTypeByUid: (layoutUid2, transaction) => this.getDesktopRouteUiLayoutTypeByUid(layoutUid2, transaction),
|
|
4450
4583
|
getPortalLayoutTypeByUid: async (targetPortalUid, transaction) => {
|
|
4451
4584
|
if ((resolvedPortal == null ? void 0 : resolvedPortal.uid) === targetPortalUid) {
|
|
@@ -6515,18 +6648,18 @@ class FlowSurfacesService {
|
|
|
6515
6648
|
currentRoles: options.currentRoles,
|
|
6516
6649
|
transaction: options.transaction
|
|
6517
6650
|
});
|
|
6518
|
-
await this.
|
|
6651
|
+
await this.assertRouteBelongsToResolvedPortal(
|
|
6519
6652
|
"updateMenu",
|
|
6520
|
-
|
|
6521
|
-
portal
|
|
6653
|
+
route,
|
|
6654
|
+
portal,
|
|
6522
6655
|
"values.menuRouteId",
|
|
6523
6656
|
options.transaction
|
|
6524
6657
|
);
|
|
6525
6658
|
if (nextParentRoute) {
|
|
6526
|
-
await this.
|
|
6659
|
+
await this.assertRouteBelongsToResolvedPortal(
|
|
6527
6660
|
"updateMenu",
|
|
6528
|
-
|
|
6529
|
-
portal
|
|
6661
|
+
nextParentRoute,
|
|
6662
|
+
portal,
|
|
6530
6663
|
"values.parentMenuRouteId",
|
|
6531
6664
|
options.transaction
|
|
6532
6665
|
);
|
package/package.json
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"displayName.zh-CN": "前端流引擎",
|
|
5
5
|
"description": "",
|
|
6
6
|
"description.zh-CN": "",
|
|
7
|
-
"version": "2.2.0-alpha.
|
|
7
|
+
"version": "2.2.0-alpha.12",
|
|
8
8
|
"main": "./dist/server/index.js",
|
|
9
9
|
"license": "Apache-2.0",
|
|
10
10
|
"devDependencies": {
|
|
@@ -26,5 +26,5 @@
|
|
|
26
26
|
"@nocobase/test": "2.x",
|
|
27
27
|
"@nocobase/utils": "2.x"
|
|
28
28
|
},
|
|
29
|
-
"gitHead": "
|
|
29
|
+
"gitHead": "96ae69754a6398de63037ead3a4aad5e5d0ab918"
|
|
30
30
|
}
|