@jskit-ai/ui-generator 0.1.5 → 0.1.6
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/README.md +104 -4
- package/package.descriptor.mjs +72 -5
- package/package.json +2 -2
- package/src/server/buildTemplateContext.js +117 -1
- package/src/server/subcommands/container.js +269 -56
- package/src/server/subcommands/outlet.js +274 -0
- package/test/buildTemplateContext.test.js +43 -0
- package/test/containerSubcommand.test.js +181 -7
- package/test/outletSubcommand.test.js +219 -0
package/README.md
CHANGED
|
@@ -46,28 +46,128 @@ Generate a route container page with nested outlet (for embedded sub-pages):
|
|
|
46
46
|
npx jskit generate @jskit-ai/ui-generator container --name "Practice" --surface admin
|
|
47
47
|
```
|
|
48
48
|
|
|
49
|
+
Generate a route container with explicit dynamic route path:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
npx jskit generate @jskit-ai/ui-generator container --name "Contact" --surface admin --directory-prefix contacts --route-path "[contactId]"
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Add a shell menu entry for that container (optional):
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
npx jskit generate @jskit-ai/ui-generator container --name "Practice" --surface admin --placement shell-layout:primary-menu
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Inject an inline outlet into an existing Vue page/component:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
npx jskit generate @jskit-ai/ui-generator outlet --file src/pages/w/[workspaceSlug]/admin/contacts/[contactId]/index.vue --host contact-view
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Show generator and subcommand help:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
npx jskit generate @jskit-ai/ui-generator help
|
|
71
|
+
npx jskit generate @jskit-ai/ui-generator outlet help
|
|
72
|
+
npx jskit generate @jskit-ai/ui-generator outlet
|
|
73
|
+
```
|
|
74
|
+
|
|
49
75
|
## Commands
|
|
50
76
|
|
|
51
77
|
- `page`: `--name --surface [--directory-prefix] [--placement]`
|
|
52
78
|
- `element`: `--name --surface [--path] [--placement]`
|
|
53
|
-
- `container`: `--name --surface [--directory-prefix] [--placement]`
|
|
79
|
+
- `container`: `--name --surface [--directory-prefix] [--route-path] [--placement]`
|
|
80
|
+
- `outlet`: `--file --host [--position] [--mode]`
|
|
81
|
+
|
|
82
|
+
`page` also supports:
|
|
83
|
+
|
|
84
|
+
- `--placement-component-token` to override the placement component token.
|
|
85
|
+
- `--placement-to` to set explicit `props.to` in the generated placement block.
|
|
86
|
+
- if `--placement-to` is omitted and `--directory-prefix` includes a `(nestedChildren)` route group, `props.to` is auto-set to `./<page-slug>`.
|
|
54
87
|
|
|
55
88
|
## Container Workflow
|
|
56
89
|
|
|
57
90
|
- `container` creates app-owned scaffolding:
|
|
58
91
|
- `src/components/SectionContainerShell.vue` (shared container shell with responsive tab row)
|
|
59
|
-
- `src/components/
|
|
60
|
-
- `packages/main/src/client/providers/MainClientProvider.js` registration for `local.main.ui.
|
|
61
|
-
- `<route>.vue` as a thin wrapper around `SectionContainerShell` + `<RouterView
|
|
92
|
+
- `src/components/TabLinkItem.vue` (tab link item token component)
|
|
93
|
+
- `packages/main/src/client/providers/MainClientProvider.js` registration for `local.main.ui.tab-link-item`
|
|
94
|
+
- `<route>.vue` as a thin wrapper around `SectionContainerShell` + `<RouterView />`, with route meta outlet declaration at `meta.jskit.placements.outlets`
|
|
95
|
+
- no shell menu placement is added unless `--placement` is explicitly provided
|
|
62
96
|
- Generate CRUD pages into that container using `@jskit-ai/crud-ui-generator` with:
|
|
63
97
|
- `--container <route-slug>`
|
|
64
98
|
- `--route-path <resource-slug>`
|
|
65
99
|
- optional `--placement` override (default becomes `<container>:sub-pages` for list pages)
|
|
66
100
|
|
|
101
|
+
## Inline Outlet Workflow
|
|
102
|
+
|
|
103
|
+
- `outlet` patches an app-owned Vue SFC by adding:
|
|
104
|
+
- `import ShellOutlet from "@jskit-ai/shell-web/client/components/ShellOutlet";`
|
|
105
|
+
- `<ShellOutlet host="<host>" position="<position>" />` in template
|
|
106
|
+
- optional `<RouterView />` (when `--mode routed`, only if one does not already exist in the file)
|
|
107
|
+
- `--mode` supports:
|
|
108
|
+
- `routed` (default): insert `RouterView` if missing
|
|
109
|
+
- `outlet-only`: insert only `ShellOutlet`
|
|
110
|
+
|
|
111
|
+
## End-to-End Example: Embed Pets CRUD in Contact View
|
|
112
|
+
|
|
113
|
+
Goal: render pets CRUD pages inside `contacts/[contactId]/index.vue` using a routed outlet and tab-style placement links.
|
|
114
|
+
|
|
115
|
+
1. Inject a routed outlet into the contact page:
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
npx jskit generate ui-generator outlet \
|
|
119
|
+
--file src/pages/w/[workspaceSlug]/admin/contacts/[contactId]/index.vue \
|
|
120
|
+
--host contact-view \
|
|
121
|
+
--position sub-pages \
|
|
122
|
+
--mode routed
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
What each option does:
|
|
126
|
+
|
|
127
|
+
- `--file`: target Vue SFC to patch.
|
|
128
|
+
- `--host`: outlet host namespace (used later by placements).
|
|
129
|
+
- `--position`: outlet position key under that host.
|
|
130
|
+
- `--mode routed`: ensures `<RouterView />` exists so nested pages render inline.
|
|
131
|
+
|
|
132
|
+
2. Generate pets CRUD pages under the nested-children group and place a tab link into that outlet:
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
npx jskit generate crud-ui-generator \
|
|
136
|
+
--namespace pets \
|
|
137
|
+
--surface admin \
|
|
138
|
+
--operations list,view,new,edit \
|
|
139
|
+
--resource-file packages/pets/src/shared/petResource.js \
|
|
140
|
+
--directory-prefix "contacts/[contactId]/(nestedChildren)" \
|
|
141
|
+
--placement contact-view:sub-pages \
|
|
142
|
+
--placement-component-token local.main.ui.tab-link-item \
|
|
143
|
+
--placement-to ./pets \
|
|
144
|
+
--id-param petId
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
What each option does:
|
|
148
|
+
|
|
149
|
+
- `--namespace pets`: CRUD namespace for generated UI artifacts.
|
|
150
|
+
- `--surface admin`: generate pages under admin surface routes.
|
|
151
|
+
- `--operations list,view,new,edit`: generate full CRUD page set.
|
|
152
|
+
- `--resource-file`: resource contract used to scaffold fields/forms.
|
|
153
|
+
- `--directory-prefix "contacts/[contactId]/(nestedChildren)"`: place generated routes under the contact context, in a route-group folder that does not appear in URL.
|
|
154
|
+
- `--placement contact-view:sub-pages`: append a placement targeting the outlet created in step 1.
|
|
155
|
+
- `--placement-component-token local.main.ui.tab-link-item`: render placement as a tab link component.
|
|
156
|
+
- `--placement-to ./pets`: tab link resolves relative to current contact route (for example `/contacts/538779/pets`).
|
|
157
|
+
- `--id-param petId`: dynamic route parameter for view/edit pages.
|
|
158
|
+
|
|
159
|
+
Expected result:
|
|
160
|
+
|
|
161
|
+
- Contact page keeps its own route and renders nested pets pages inline via `RouterView`.
|
|
162
|
+
- Pets routes are generated under `contacts/[contactId]/(nestedChildren)/pets/...`.
|
|
163
|
+
- URL remains clean (`(nestedChildren)` is not part of URL).
|
|
164
|
+
- A placement entry is added so the pets tab appears in `contact-view:sub-pages`.
|
|
165
|
+
|
|
67
166
|
## Placement Notes
|
|
68
167
|
|
|
69
168
|
- `--placement` expects `host:position`.
|
|
70
169
|
- Targets come from:
|
|
71
170
|
- app-declared `<ShellOutlet host="..." position="..." />` in `src/**/*.vue`
|
|
171
|
+
- app route meta `meta.jskit.placements.outlets` declarations in `src/**/*.vue`
|
|
72
172
|
- installed package metadata `metadata.ui.placements.outlets`
|
|
73
173
|
- If `--placement` is omitted, the app default outlet is used.
|
package/package.descriptor.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export default Object.freeze({
|
|
2
2
|
packageVersion: 1,
|
|
3
3
|
packageId: "@jskit-ai/ui-generator",
|
|
4
|
-
version: "0.1.
|
|
4
|
+
version: "0.1.6",
|
|
5
5
|
kind: "generator",
|
|
6
6
|
description: "Generate app-local non-CRUD UI pages and outlet elements.",
|
|
7
7
|
options: {
|
|
@@ -33,12 +33,64 @@ export default Object.freeze({
|
|
|
33
33
|
promptLabel: "Page directory prefix",
|
|
34
34
|
promptHint: "Optional subpath under the selected surface pages root (example: crm or ops/team-a)."
|
|
35
35
|
},
|
|
36
|
+
"route-path": {
|
|
37
|
+
required: false,
|
|
38
|
+
inputType: "text",
|
|
39
|
+
defaultValue: "",
|
|
40
|
+
promptLabel: "Route path",
|
|
41
|
+
promptHint:
|
|
42
|
+
"Optional explicit container route path (example: contact-tools or contacts/[contactId]). Defaults to --name."
|
|
43
|
+
},
|
|
36
44
|
placement: {
|
|
37
45
|
required: false,
|
|
38
46
|
inputType: "text",
|
|
39
47
|
defaultValue: "",
|
|
40
48
|
promptLabel: "Placement target",
|
|
41
49
|
promptHint: "Optional host:position target (defaults to app ShellOutlet default target)."
|
|
50
|
+
},
|
|
51
|
+
"placement-component-token": {
|
|
52
|
+
required: false,
|
|
53
|
+
inputType: "text",
|
|
54
|
+
defaultValue: "",
|
|
55
|
+
promptLabel: "Placement component token",
|
|
56
|
+
promptHint:
|
|
57
|
+
"Optional component token override for generated menu placement (example: local.main.ui.tab-link-item)."
|
|
58
|
+
},
|
|
59
|
+
"placement-to": {
|
|
60
|
+
required: false,
|
|
61
|
+
inputType: "text",
|
|
62
|
+
defaultValue: "",
|
|
63
|
+
promptLabel: "Placement to",
|
|
64
|
+
promptHint:
|
|
65
|
+
"Optional explicit props.to value for generated menu placement (example: ./notes). If omitted and directory-prefix includes a nestedChildren route group, defaults to ./<page-slug>."
|
|
66
|
+
},
|
|
67
|
+
file: {
|
|
68
|
+
required: false,
|
|
69
|
+
inputType: "text",
|
|
70
|
+
defaultValue: "",
|
|
71
|
+
promptLabel: "Target Vue file",
|
|
72
|
+
promptHint: "Vue SFC path relative to app root (used by outlet subcommand)."
|
|
73
|
+
},
|
|
74
|
+
host: {
|
|
75
|
+
required: false,
|
|
76
|
+
inputType: "text",
|
|
77
|
+
defaultValue: "",
|
|
78
|
+
promptLabel: "Outlet host",
|
|
79
|
+
promptHint: "ShellOutlet host value to inject into target file."
|
|
80
|
+
},
|
|
81
|
+
position: {
|
|
82
|
+
required: false,
|
|
83
|
+
inputType: "text",
|
|
84
|
+
defaultValue: "sub-pages",
|
|
85
|
+
promptLabel: "Outlet position",
|
|
86
|
+
promptHint: "ShellOutlet position value to inject into target file."
|
|
87
|
+
},
|
|
88
|
+
mode: {
|
|
89
|
+
required: false,
|
|
90
|
+
inputType: "text",
|
|
91
|
+
defaultValue: "routed",
|
|
92
|
+
promptLabel: "Outlet mode",
|
|
93
|
+
promptHint: "routed | outlet-only (routed injects RouterView when missing)."
|
|
42
94
|
}
|
|
43
95
|
},
|
|
44
96
|
dependsOn: [],
|
|
@@ -57,13 +109,28 @@ export default Object.freeze({
|
|
|
57
109
|
metadata: {
|
|
58
110
|
generatorPrimarySubcommand: "page",
|
|
59
111
|
generatorSubcommands: {
|
|
112
|
+
page: {
|
|
113
|
+
description: "Scaffold a non-CRUD page and add a menu placement entry.",
|
|
114
|
+
optionNames: ["name", "surface", "directory-prefix", "placement", "placement-component-token", "placement-to"]
|
|
115
|
+
},
|
|
60
116
|
element: {
|
|
61
117
|
entrypoint: "src/server/subcommands/element.js",
|
|
62
|
-
export: "runGeneratorSubcommand"
|
|
118
|
+
export: "runGeneratorSubcommand",
|
|
119
|
+
description: "Scaffold a reusable UI element component and register a placement.",
|
|
120
|
+
optionNames: ["name", "surface", "path", "placement"]
|
|
63
121
|
},
|
|
64
122
|
container: {
|
|
65
123
|
entrypoint: "src/server/subcommands/container.js",
|
|
66
|
-
export: "runGeneratorSubcommand"
|
|
124
|
+
export: "runGeneratorSubcommand",
|
|
125
|
+
description: "Scaffold a routed section container page with a tab outlet. Adds a menu entry only when --placement is passed.",
|
|
126
|
+
optionNames: ["name", "surface", "directory-prefix", "route-path", "path", "placement"]
|
|
127
|
+
},
|
|
128
|
+
outlet: {
|
|
129
|
+
entrypoint: "src/server/subcommands/outlet.js",
|
|
130
|
+
export: "runGeneratorSubcommand",
|
|
131
|
+
description: "Inject a ShellOutlet block into an existing Vue page/component.",
|
|
132
|
+
optionNames: ["file", "host", "position", "mode"],
|
|
133
|
+
requiredOptionNames: ["file", "host"]
|
|
67
134
|
}
|
|
68
135
|
},
|
|
69
136
|
apiSummary: {
|
|
@@ -82,7 +149,7 @@ export default Object.freeze({
|
|
|
82
149
|
mutations: {
|
|
83
150
|
dependencies: {
|
|
84
151
|
runtime: {
|
|
85
|
-
"@jskit-ai/users-web": "0.1.
|
|
152
|
+
"@jskit-ai/users-web": "0.1.37"
|
|
86
153
|
},
|
|
87
154
|
dev: {}
|
|
88
155
|
},
|
|
@@ -107,7 +174,7 @@ export default Object.freeze({
|
|
|
107
174
|
position: "bottom",
|
|
108
175
|
skipIfContains: "jskit:ui-generator.page.menu:${option:surface|lower}:${option:directory-prefix|path}:${option:name|path}",
|
|
109
176
|
value:
|
|
110
|
-
"\n// jskit:ui-generator.page.menu:${option:surface|lower}:${option:directory-prefix|path}:${option:name|path}\n{\n addPlacement({\n id: \"ui-generator.page.${option:name|kebab}.menu\",\n host: \"__JSKIT_UI_MENU_PLACEMENT_HOST__\",\n position: \"__JSKIT_UI_MENU_PLACEMENT_POSITION__\",\n surfaces: [\"${option:surface|lower}\"],\n order: 155,\n componentToken: \"
|
|
177
|
+
"\n// jskit:ui-generator.page.menu:${option:surface|lower}:${option:directory-prefix|path}:${option:name|path}\n{\n addPlacement({\n id: \"ui-generator.page.${option:name|kebab}.menu\",\n host: \"__JSKIT_UI_MENU_PLACEMENT_HOST__\",\n position: \"__JSKIT_UI_MENU_PLACEMENT_POSITION__\",\n surfaces: [\"${option:surface|lower}\"],\n order: 155,\n componentToken: \"__JSKIT_UI_MENU_COMPONENT_TOKEN__\",\n props: {\n label: \"${option:name|trim}\",\n surface: \"${option:surface|lower}\",\n workspaceSuffix: \"__JSKIT_UI_MENU_WORKSPACE_SUFFIX__\",\n nonWorkspaceSuffix: \"__JSKIT_UI_MENU_NON_WORKSPACE_SUFFIX__\",\n__JSKIT_UI_MENU_TO_PROP_LINE__ },\n when: ({ auth }) => Boolean(auth?.authenticated)\n });\n}\n",
|
|
111
178
|
reason: "Append generated UI page menu placement.",
|
|
112
179
|
category: "ui-generator",
|
|
113
180
|
id: "ui-generator-page-placement-menu-${option:name|snake}",
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jskit-ai/ui-generator",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"test": "node --test"
|
|
7
7
|
},
|
|
8
8
|
"dependencies": {
|
|
9
|
-
"@jskit-ai/kernel": "0.1.
|
|
9
|
+
"@jskit-ai/kernel": "0.1.23"
|
|
10
10
|
},
|
|
11
11
|
"exports": {
|
|
12
12
|
"./server/buildTemplateContext": "./src/server/buildTemplateContext.js"
|
|
@@ -1,6 +1,118 @@
|
|
|
1
1
|
import { normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
|
|
2
2
|
import { resolveShellOutletPlacementTargetFromApp } from "@jskit-ai/kernel/server/support";
|
|
3
3
|
|
|
4
|
+
const DEFAULT_MENU_COMPONENT_TOKEN = "users.web.shell.surface-aware-menu-link-item";
|
|
5
|
+
const NESTED_CHILDREN_GROUPS = new Set(["nestedchildren", "nested-children"]);
|
|
6
|
+
|
|
7
|
+
function splitTextIntoWords(value = "") {
|
|
8
|
+
const normalized = String(value || "")
|
|
9
|
+
.replace(/([a-z0-9])([A-Z])/g, "$1 $2")
|
|
10
|
+
.replace(/[^A-Za-z0-9]+/g, " ")
|
|
11
|
+
.trim();
|
|
12
|
+
if (!normalized) {
|
|
13
|
+
return [];
|
|
14
|
+
}
|
|
15
|
+
return normalized
|
|
16
|
+
.split(/\s+/)
|
|
17
|
+
.map((entry) => entry.toLowerCase())
|
|
18
|
+
.filter(Boolean);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function wordsToKebab(words = []) {
|
|
22
|
+
return (Array.isArray(words) ? words : [])
|
|
23
|
+
.map((entry) => String(entry || "").toLowerCase())
|
|
24
|
+
.filter(Boolean)
|
|
25
|
+
.join("-");
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function normalizePathValue(value = "") {
|
|
29
|
+
return String(value || "")
|
|
30
|
+
.replaceAll("\\", "/")
|
|
31
|
+
.split("/")
|
|
32
|
+
.map((segment) => {
|
|
33
|
+
const normalizedSegment = normalizeText(segment);
|
|
34
|
+
if (!normalizedSegment) {
|
|
35
|
+
return "";
|
|
36
|
+
}
|
|
37
|
+
if (/^\[[^\]]+\]$/.test(normalizedSegment)) {
|
|
38
|
+
return normalizedSegment;
|
|
39
|
+
}
|
|
40
|
+
const routeGroupMatch = /^\(([^()]+)\)$/.exec(normalizedSegment);
|
|
41
|
+
if (routeGroupMatch) {
|
|
42
|
+
const routeGroupName = wordsToKebab(splitTextIntoWords(routeGroupMatch[1]));
|
|
43
|
+
return routeGroupName ? `(${routeGroupName})` : "";
|
|
44
|
+
}
|
|
45
|
+
return wordsToKebab(splitTextIntoWords(normalizedSegment));
|
|
46
|
+
})
|
|
47
|
+
.filter(Boolean)
|
|
48
|
+
.join("/");
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function splitPathSegments(value = "") {
|
|
52
|
+
return normalizePathValue(value)
|
|
53
|
+
.split("/")
|
|
54
|
+
.map((entry) => normalizeText(entry))
|
|
55
|
+
.filter(Boolean);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function isRouteGroupSegment(value = "") {
|
|
59
|
+
const source = normalizeText(value);
|
|
60
|
+
return source.startsWith("(") && source.endsWith(")");
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function isNestedChildrenRouteGroupSegment(value = "") {
|
|
64
|
+
const source = normalizeText(value);
|
|
65
|
+
if (!isRouteGroupSegment(source)) {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
const groupName = source.slice(1, -1).trim().toLowerCase();
|
|
69
|
+
return NESTED_CHILDREN_GROUPS.has(groupName);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function resolvePlacementUrlSuffix(options = {}) {
|
|
73
|
+
const routeSegments = [
|
|
74
|
+
...splitPathSegments(options?.["directory-prefix"]),
|
|
75
|
+
...splitPathSegments(options?.name)
|
|
76
|
+
].filter((segment) => !isRouteGroupSegment(segment));
|
|
77
|
+
if (routeSegments.length < 1) {
|
|
78
|
+
return "/";
|
|
79
|
+
}
|
|
80
|
+
return `/${routeSegments.join("/")}`;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function resolveMenuComponentToken(options = {}) {
|
|
84
|
+
const explicitToken = normalizeText(options?.["placement-component-token"]);
|
|
85
|
+
if (explicitToken) {
|
|
86
|
+
return explicitToken;
|
|
87
|
+
}
|
|
88
|
+
return DEFAULT_MENU_COMPONENT_TOKEN;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function resolveAutoRelativePlacementTo(options = {}) {
|
|
92
|
+
const explicitPlacementTo = normalizeText(options?.["placement-to"]);
|
|
93
|
+
if (explicitPlacementTo) {
|
|
94
|
+
return explicitPlacementTo;
|
|
95
|
+
}
|
|
96
|
+
const directorySegments = splitPathSegments(options?.["directory-prefix"]);
|
|
97
|
+
const hasNestedChildrenGroup = directorySegments.some((segment) => isNestedChildrenRouteGroupSegment(segment));
|
|
98
|
+
if (!hasNestedChildrenGroup) {
|
|
99
|
+
return "";
|
|
100
|
+
}
|
|
101
|
+
const pagePath = normalizePathValue(options?.name);
|
|
102
|
+
if (!pagePath) {
|
|
103
|
+
return "";
|
|
104
|
+
}
|
|
105
|
+
return `./${pagePath}`;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function resolveMenuToPropLine(options = {}) {
|
|
109
|
+
const placementTo = resolveAutoRelativePlacementTo(options);
|
|
110
|
+
if (!placementTo) {
|
|
111
|
+
return "";
|
|
112
|
+
}
|
|
113
|
+
return ` to: ${JSON.stringify(placementTo)},\n`;
|
|
114
|
+
}
|
|
115
|
+
|
|
4
116
|
async function buildUiPageTemplateContext({ appRoot, options } = {}) {
|
|
5
117
|
const placementTarget = await resolveShellOutletPlacementTargetFromApp({
|
|
6
118
|
appRoot,
|
|
@@ -10,7 +122,11 @@ async function buildUiPageTemplateContext({ appRoot, options } = {}) {
|
|
|
10
122
|
|
|
11
123
|
return {
|
|
12
124
|
__JSKIT_UI_MENU_PLACEMENT_HOST__: normalizeText(placementTarget?.host),
|
|
13
|
-
__JSKIT_UI_MENU_PLACEMENT_POSITION__: normalizeText(placementTarget?.position)
|
|
125
|
+
__JSKIT_UI_MENU_PLACEMENT_POSITION__: normalizeText(placementTarget?.position),
|
|
126
|
+
__JSKIT_UI_MENU_COMPONENT_TOKEN__: resolveMenuComponentToken(options),
|
|
127
|
+
__JSKIT_UI_MENU_WORKSPACE_SUFFIX__: resolvePlacementUrlSuffix(options),
|
|
128
|
+
__JSKIT_UI_MENU_NON_WORKSPACE_SUFFIX__: resolvePlacementUrlSuffix(options),
|
|
129
|
+
__JSKIT_UI_MENU_TO_PROP_LINE__: resolveMenuToPropLine(options)
|
|
14
130
|
};
|
|
15
131
|
}
|
|
16
132
|
|