@jskit-ai/users-web 0.1.47 → 0.1.49
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/package.descriptor.mjs +87 -32
- package/package.json +7 -9
- package/src/client/components/MembersAdminClientElement.vue +5 -4
- package/src/client/components/UsersHomeToolsWidget.vue +12 -0
- package/src/client/components/UsersWorkspaceToolsWidget.vue +7 -18
- package/src/client/components/WorkspaceMembersClientElement.vue +18 -16
- package/src/client/composables/account-settings/accountSettingsRuntimeHelpers.js +4 -3
- package/src/client/index.js +0 -2
- package/src/client/lib/bootstrap.js +6 -4
- package/src/client/providers/UsersWebClientProvider.js +2 -4
- package/src/client/providers/UsersWorkspacesClientProvider.js +0 -2
- package/src/shared/toolsOutletContracts.js +19 -0
- package/templates/src/pages/console/settings/index.vue +5 -4
- package/templates/src/pages/console/settings.vue +4 -1
- package/test/bootstrap.test.js +2 -2
- package/test/bootstrapPlacementRuntime.test.js +31 -31
- package/test/settingsPlacementContract.test.js +193 -6
- package/src/client/components/UsersShellMenuLinkItem.vue +0 -140
- package/src/client/components/UsersSurfaceAwareMenuLinkItem.vue +0 -76
package/package.descriptor.mjs
CHANGED
|
@@ -1,10 +1,16 @@
|
|
|
1
|
+
import {
|
|
2
|
+
HOME_TOOLS_OUTLET,
|
|
3
|
+
WORKSPACE_TOOLS_OUTLET
|
|
4
|
+
} from "./src/shared/toolsOutletContracts.js";
|
|
5
|
+
|
|
1
6
|
export default Object.freeze({
|
|
2
7
|
packageVersion: 1,
|
|
3
8
|
packageId: "@jskit-ai/users-web",
|
|
4
|
-
version: "0.1.
|
|
9
|
+
version: "0.1.49",
|
|
5
10
|
kind: "runtime",
|
|
6
|
-
description: "Users web module: account/profile UI plus shared
|
|
11
|
+
description: "Users web module: account/profile UI plus shared users web widgets.",
|
|
7
12
|
dependsOn: [
|
|
13
|
+
"@jskit-ai/auth-web",
|
|
8
14
|
"@jskit-ai/http-runtime",
|
|
9
15
|
"@jskit-ai/shell-web",
|
|
10
16
|
"@jskit-ai/uploads-image-web",
|
|
@@ -47,14 +53,6 @@ export default Object.freeze({
|
|
|
47
53
|
subpath: "./client/components/ProfileClientElement",
|
|
48
54
|
summary: "Exports profile settings client element scaffold component."
|
|
49
55
|
},
|
|
50
|
-
{
|
|
51
|
-
subpath: "./client/components/ConsoleSettingsClientElement",
|
|
52
|
-
summary: "Exports console settings landing-page client element."
|
|
53
|
-
},
|
|
54
|
-
{
|
|
55
|
-
subpath: "./client/components/WorkspaceSettingsClientElement",
|
|
56
|
-
summary: "Exports workspace settings client element."
|
|
57
|
-
},
|
|
58
56
|
{
|
|
59
57
|
subpath: "./client/composables/useAddEdit",
|
|
60
58
|
summary: "Exports add/edit operation composable."
|
|
@@ -95,9 +93,8 @@ export default Object.freeze({
|
|
|
95
93
|
containerTokens: {
|
|
96
94
|
server: [],
|
|
97
95
|
client: [
|
|
98
|
-
"users.web.shell.menu-link-item",
|
|
99
|
-
"users.web.shell.surface-aware-menu-link-item",
|
|
100
96
|
"users.web.profile.menu.surface-switch-item",
|
|
97
|
+
"users.web.home.tools.widget",
|
|
101
98
|
"users.web.profile.element",
|
|
102
99
|
"users.web.bootstrap-placement.runtime"
|
|
103
100
|
]
|
|
@@ -107,8 +104,20 @@ export default Object.freeze({
|
|
|
107
104
|
placements: {
|
|
108
105
|
outlets: [
|
|
109
106
|
{
|
|
110
|
-
|
|
111
|
-
|
|
107
|
+
target: HOME_TOOLS_OUTLET.target,
|
|
108
|
+
defaultLinkComponentToken: HOME_TOOLS_OUTLET.defaultLinkComponentToken,
|
|
109
|
+
surfaces: ["home"],
|
|
110
|
+
source: "src/client/components/UsersHomeToolsWidget.vue"
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
target: WORKSPACE_TOOLS_OUTLET.target,
|
|
114
|
+
defaultLinkComponentToken: WORKSPACE_TOOLS_OUTLET.defaultLinkComponentToken,
|
|
115
|
+
surfaces: ["admin"],
|
|
116
|
+
source: "src/client/components/UsersWorkspaceToolsWidget.vue"
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
target: "console-settings:primary-menu",
|
|
120
|
+
defaultLinkComponentToken: "local.main.ui.surface-aware-menu-link-item",
|
|
112
121
|
surfaces: ["console"],
|
|
113
122
|
source: "templates/src/pages/console/settings.vue"
|
|
114
123
|
}
|
|
@@ -116,8 +125,7 @@ export default Object.freeze({
|
|
|
116
125
|
contributions: [
|
|
117
126
|
{
|
|
118
127
|
id: "users.profile.menu.surface-switch",
|
|
119
|
-
|
|
120
|
-
position: "primary-menu",
|
|
128
|
+
target: "auth-profile-menu:primary-menu",
|
|
121
129
|
surfaces: ["*"],
|
|
122
130
|
order: 100,
|
|
123
131
|
componentToken: "users.web.profile.menu.surface-switch-item",
|
|
@@ -126,23 +134,48 @@ export default Object.freeze({
|
|
|
126
134
|
},
|
|
127
135
|
{
|
|
128
136
|
id: "users.profile.menu.settings",
|
|
129
|
-
|
|
130
|
-
position: "primary-menu",
|
|
137
|
+
target: "auth-profile-menu:primary-menu",
|
|
131
138
|
surfaces: ["*"],
|
|
132
139
|
order: 500,
|
|
133
|
-
componentToken: "
|
|
140
|
+
componentToken: "auth.web.profile.menu.link-item",
|
|
134
141
|
when: "auth.authenticated === true",
|
|
135
142
|
source: "mutations.text#users-web-profile-settings-placement"
|
|
136
143
|
},
|
|
144
|
+
{
|
|
145
|
+
id: "users.home.menu.home",
|
|
146
|
+
target: "shell-layout:primary-menu",
|
|
147
|
+
surfaces: ["*"],
|
|
148
|
+
order: 50,
|
|
149
|
+
componentToken: "local.main.ui.surface-aware-menu-link-item",
|
|
150
|
+
when: "auth.authenticated === true",
|
|
151
|
+
source: "mutations.text#users-web-home-shell-menu-placement"
|
|
152
|
+
},
|
|
137
153
|
{
|
|
138
154
|
id: "users.console.menu.settings",
|
|
139
|
-
|
|
140
|
-
position: "primary-menu",
|
|
155
|
+
target: "shell-layout:primary-menu",
|
|
141
156
|
surfaces: ["console"],
|
|
142
157
|
order: 100,
|
|
143
|
-
componentToken: "
|
|
158
|
+
componentToken: "local.main.ui.menu-link-item",
|
|
144
159
|
when: "auth.authenticated === true",
|
|
145
160
|
source: "mutations.text#users-web-console-settings-placement"
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
id: "users.home.tools.widget",
|
|
164
|
+
target: "shell-layout:top-right",
|
|
165
|
+
surfaces: ["home"],
|
|
166
|
+
order: 900,
|
|
167
|
+
componentToken: "users.web.home.tools.widget",
|
|
168
|
+
when: "auth.authenticated === true",
|
|
169
|
+
source: "mutations.text#users-web-home-tools-placement"
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
id: "users.home.menu.settings",
|
|
173
|
+
target: "home-tools:primary-menu",
|
|
174
|
+
surfaces: ["home"],
|
|
175
|
+
order: 100,
|
|
176
|
+
componentToken: "local.main.ui.surface-aware-menu-link-item",
|
|
177
|
+
when: "auth.authenticated === true",
|
|
178
|
+
source: "mutations.text#users-web-home-tools-placement"
|
|
146
179
|
}
|
|
147
180
|
]
|
|
148
181
|
}
|
|
@@ -153,12 +186,12 @@ export default Object.freeze({
|
|
|
153
186
|
runtime: {
|
|
154
187
|
"@tanstack/vue-query": "5.92.12",
|
|
155
188
|
"@mdi/js": "^7.4.47",
|
|
156
|
-
"@jskit-ai/http-runtime": "0.1.
|
|
157
|
-
"@jskit-ai/realtime": "0.1.
|
|
158
|
-
"@jskit-ai/kernel": "0.1.
|
|
159
|
-
"@jskit-ai/shell-web": "0.1.
|
|
160
|
-
"@jskit-ai/uploads-image-web": "0.1.
|
|
161
|
-
"@jskit-ai/users-core": "0.1.
|
|
189
|
+
"@jskit-ai/http-runtime": "0.1.33",
|
|
190
|
+
"@jskit-ai/realtime": "0.1.33",
|
|
191
|
+
"@jskit-ai/kernel": "0.1.34",
|
|
192
|
+
"@jskit-ai/shell-web": "0.1.33",
|
|
193
|
+
"@jskit-ai/uploads-image-web": "0.1.12",
|
|
194
|
+
"@jskit-ai/users-core": "0.1.44",
|
|
162
195
|
vuetify: "^4.0.0"
|
|
163
196
|
},
|
|
164
197
|
dev: {}
|
|
@@ -226,7 +259,7 @@ export default Object.freeze({
|
|
|
226
259
|
from: "templates/src/pages/console/settings/index.vue",
|
|
227
260
|
toSurface: "console",
|
|
228
261
|
toSurfacePath: "settings/index.vue",
|
|
229
|
-
reason: "Install console settings
|
|
262
|
+
reason: "Install console settings index stub scaffold for app-owned landing or redirect behavior.",
|
|
230
263
|
category: "users-web",
|
|
231
264
|
id: "users-web-page-console-settings"
|
|
232
265
|
}
|
|
@@ -249,7 +282,7 @@ export default Object.freeze({
|
|
|
249
282
|
position: "bottom",
|
|
250
283
|
skipIfContains: "id: \"users.profile.menu.surface-switch\"",
|
|
251
284
|
value:
|
|
252
|
-
"\naddPlacement({\n id: \"users.profile.menu.surface-switch\",\n
|
|
285
|
+
"\naddPlacement({\n id: \"users.profile.menu.surface-switch\",\n target: \"auth-profile-menu:primary-menu\",\n surfaces: [\"*\"],\n order: 100,\n componentToken: \"users.web.profile.menu.surface-switch-item\",\n when: ({ auth }) => Boolean(auth?.authenticated)\n});\n",
|
|
253
286
|
reason: "Append users-web profile surface switch placement into app-owned placement registry.",
|
|
254
287
|
category: "users-web",
|
|
255
288
|
id: "users-web-profile-surface-switch-placement"
|
|
@@ -260,21 +293,43 @@ export default Object.freeze({
|
|
|
260
293
|
position: "bottom",
|
|
261
294
|
skipIfContains: "id: \"users.profile.menu.settings\"",
|
|
262
295
|
value:
|
|
263
|
-
"\naddPlacement({\n id: \"users.profile.menu.settings\",\n
|
|
296
|
+
"\naddPlacement({\n id: \"users.profile.menu.settings\",\n target: \"auth-profile-menu:primary-menu\",\n surfaces: [\"*\"],\n order: 500,\n componentToken: \"auth.web.profile.menu.link-item\",\n props: {\n label: \"Settings\",\n to: \"/account\"\n },\n when: ({ auth }) => Boolean(auth?.authenticated)\n});\n",
|
|
264
297
|
reason: "Append users-web profile settings menu placement into app-owned placement registry.",
|
|
265
298
|
category: "users-web",
|
|
266
299
|
id: "users-web-profile-settings-placement"
|
|
267
300
|
},
|
|
301
|
+
{
|
|
302
|
+
op: "append-text",
|
|
303
|
+
file: "src/placement.js",
|
|
304
|
+
position: "bottom",
|
|
305
|
+
skipIfContains: "id: \"users.home.menu.home\"",
|
|
306
|
+
value:
|
|
307
|
+
"\naddPlacement({\n id: \"users.home.menu.home\",\n target: \"shell-layout:primary-menu\",\n surfaces: [\"*\"],\n order: 50,\n componentToken: \"local.main.ui.surface-aware-menu-link-item\",\n props: {\n label: \"Home\",\n surface: \"home\",\n workspaceSuffix: \"/\",\n nonWorkspaceSuffix: \"/\",\n exact: true\n },\n when: ({ auth }) => Boolean(auth?.authenticated)\n});\n",
|
|
308
|
+
reason: "Append users-web home shell menu placement into app-owned placement registry.",
|
|
309
|
+
category: "users-web",
|
|
310
|
+
id: "users-web-home-shell-menu-placement"
|
|
311
|
+
},
|
|
268
312
|
{
|
|
269
313
|
op: "append-text",
|
|
270
314
|
file: "src/placement.js",
|
|
271
315
|
position: "bottom",
|
|
272
316
|
skipIfContains: "id: \"users.console.menu.settings\"",
|
|
273
317
|
value:
|
|
274
|
-
"\naddPlacement({\n id: \"users.console.menu.settings\",\n
|
|
318
|
+
"\naddPlacement({\n id: \"users.console.menu.settings\",\n target: \"shell-layout:primary-menu\",\n surfaces: [\"console\"],\n order: 100,\n componentToken: \"local.main.ui.menu-link-item\",\n props: {\n label: \"Settings\",\n to: \"/console/settings\",\n icon: \"mdi-cog-outline\"\n },\n when: ({ auth }) => Boolean(auth?.authenticated)\n});\n",
|
|
275
319
|
reason: "Append users-web console settings menu placement into app-owned placement registry.",
|
|
276
320
|
category: "users-web",
|
|
277
321
|
id: "users-web-console-settings-placement"
|
|
322
|
+
},
|
|
323
|
+
{
|
|
324
|
+
op: "append-text",
|
|
325
|
+
file: "src/placement.js",
|
|
326
|
+
position: "bottom",
|
|
327
|
+
skipIfContains: "id: \"users.home.tools.widget\"",
|
|
328
|
+
value:
|
|
329
|
+
"\naddPlacement({\n id: \"users.home.tools.widget\",\n target: \"shell-layout:top-right\",\n surfaces: [\"home\"],\n order: 900,\n componentToken: \"users.web.home.tools.widget\",\n when: ({ auth }) => Boolean(auth?.authenticated)\n});\n\naddPlacement({\n id: \"users.home.menu.settings\",\n target: \"home-tools:primary-menu\",\n surfaces: [\"home\"],\n order: 100,\n componentToken: \"local.main.ui.surface-aware-menu-link-item\",\n props: {\n label: \"Settings\",\n surface: \"home\",\n workspaceSuffix: \"/settings\",\n nonWorkspaceSuffix: \"/settings\"\n },\n when: ({ auth }) => Boolean(auth?.authenticated)\n});\n",
|
|
330
|
+
reason: "Append users-web home tools widget and settings menu placements into app-owned placement registry.",
|
|
331
|
+
category: "users-web",
|
|
332
|
+
id: "users-web-home-tools-placement"
|
|
278
333
|
}
|
|
279
334
|
]
|
|
280
335
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jskit-ai/users-web",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.49",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"test": "node --test"
|
|
@@ -8,8 +8,6 @@
|
|
|
8
8
|
"exports": {
|
|
9
9
|
"./client": "./src/client/index.js",
|
|
10
10
|
"./client/providers/UsersWorkspacesClientProvider": "./src/client/providers/UsersWorkspacesClientProvider.js",
|
|
11
|
-
"./client/components/ConsoleSettingsClientElement": "./src/client/components/ConsoleSettingsClientElement.vue",
|
|
12
|
-
"./client/components/WorkspaceSettingsClientElement": "./src/client/components/WorkspaceSettingsClientElement.vue",
|
|
13
11
|
"./client/components/WorkspaceMembersClientElement": "./src/client/components/WorkspaceMembersClientElement.vue",
|
|
14
12
|
"./client/composables/useAddEdit": "./src/client/composables/records/useAddEdit.js",
|
|
15
13
|
"./client/composables/useCrudAddEdit": "./src/client/composables/records/useCrudAddEdit.js",
|
|
@@ -28,12 +26,12 @@
|
|
|
28
26
|
"dependencies": {
|
|
29
27
|
"@tanstack/vue-query": "5.92.12",
|
|
30
28
|
"@mdi/js": "^7.4.47",
|
|
31
|
-
"@jskit-ai/http-runtime": "0.1.
|
|
32
|
-
"@jskit-ai/kernel": "0.1.
|
|
33
|
-
"@jskit-ai/realtime": "0.1.
|
|
34
|
-
"@jskit-ai/shell-web": "0.1.
|
|
35
|
-
"@jskit-ai/uploads-image-web": "0.1.
|
|
36
|
-
"@jskit-ai/users-core": "0.1.
|
|
29
|
+
"@jskit-ai/http-runtime": "0.1.33",
|
|
30
|
+
"@jskit-ai/kernel": "0.1.34",
|
|
31
|
+
"@jskit-ai/realtime": "0.1.33",
|
|
32
|
+
"@jskit-ai/shell-web": "0.1.33",
|
|
33
|
+
"@jskit-ai/uploads-image-web": "0.1.12",
|
|
34
|
+
"@jskit-ai/users-core": "0.1.44",
|
|
37
35
|
"vuetify": "^4.0.0"
|
|
38
36
|
}
|
|
39
37
|
}
|
|
@@ -168,6 +168,7 @@
|
|
|
168
168
|
<script setup>
|
|
169
169
|
import { computed, toRefs, unref } from "vue";
|
|
170
170
|
import { formatDateTime as formatKernelDateTime } from "@jskit-ai/kernel/shared/support";
|
|
171
|
+
import { normalizeRecordId } from "@jskit-ai/kernel/shared/support/normalize";
|
|
171
172
|
import { requireBoolean, requireFunction, requireRecord } from "../support/contractGuards.js";
|
|
172
173
|
|
|
173
174
|
const props = defineProps({
|
|
@@ -188,11 +189,11 @@ const props = defineProps({
|
|
|
188
189
|
required: true
|
|
189
190
|
},
|
|
190
191
|
revokeInviteId: {
|
|
191
|
-
type:
|
|
192
|
+
type: String,
|
|
192
193
|
required: true
|
|
193
194
|
},
|
|
194
195
|
removeMemberUserId: {
|
|
195
|
-
type:
|
|
196
|
+
type: String,
|
|
196
197
|
required: true
|
|
197
198
|
},
|
|
198
199
|
status: {
|
|
@@ -352,11 +353,11 @@ function isMemberRemoveLocked(member) {
|
|
|
352
353
|
}
|
|
353
354
|
|
|
354
355
|
function isRevokeInviteLoading(inviteId) {
|
|
355
|
-
return isRevokingInvite.value && revokeInviteId.value ===
|
|
356
|
+
return isRevokingInvite.value && revokeInviteId.value === normalizeRecordId(inviteId, { fallback: "" });
|
|
356
357
|
}
|
|
357
358
|
|
|
358
359
|
function isRemoveMemberLoading(memberUserId) {
|
|
359
|
-
return isRemovingMember.value && removeMemberUserId.value ===
|
|
360
|
+
return isRemovingMember.value && removeMemberUserId.value === normalizeRecordId(memberUserId, { fallback: "" });
|
|
360
361
|
}
|
|
361
362
|
|
|
362
363
|
async function onSubmitInvite() {
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import ShellOutletMenuWidget from "@jskit-ai/shell-web/client/components/ShellOutletMenuWidget";
|
|
3
|
+
import { HOME_TOOLS_OUTLET } from "../../shared/toolsOutletContracts.js";
|
|
4
|
+
</script>
|
|
5
|
+
|
|
6
|
+
<template>
|
|
7
|
+
<ShellOutletMenuWidget
|
|
8
|
+
:target="HOME_TOOLS_OUTLET.target"
|
|
9
|
+
:default-link-component-token="HOME_TOOLS_OUTLET.defaultLinkComponentToken"
|
|
10
|
+
:aria-label="HOME_TOOLS_OUTLET.ariaLabel"
|
|
11
|
+
/>
|
|
12
|
+
</template>
|
|
@@ -1,23 +1,12 @@
|
|
|
1
1
|
<script setup>
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
2
|
+
import ShellOutletMenuWidget from "@jskit-ai/shell-web/client/components/ShellOutletMenuWidget";
|
|
3
|
+
import { WORKSPACE_TOOLS_OUTLET } from "../../shared/toolsOutletContracts.js";
|
|
4
4
|
</script>
|
|
5
5
|
|
|
6
6
|
<template>
|
|
7
|
-
<
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
variant="text"
|
|
13
|
-
aria-label="Workspace tools"
|
|
14
|
-
>
|
|
15
|
-
<v-icon :icon="mdiCogOutline" />
|
|
16
|
-
</v-btn>
|
|
17
|
-
</template>
|
|
18
|
-
|
|
19
|
-
<v-list min-width="220" density="comfortable" class="py-1">
|
|
20
|
-
<ShellOutlet host="workspace-tools" position="primary-menu" />
|
|
21
|
-
</v-list>
|
|
22
|
-
</v-menu>
|
|
7
|
+
<ShellOutletMenuWidget
|
|
8
|
+
:target="WORKSPACE_TOOLS_OUTLET.target"
|
|
9
|
+
:default-link-component-token="WORKSPACE_TOOLS_OUTLET.defaultLinkComponentToken"
|
|
10
|
+
:aria-label="WORKSPACE_TOOLS_OUTLET.ariaLabel"
|
|
11
|
+
/>
|
|
23
12
|
</template>
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
<script setup>
|
|
22
22
|
import { computed, reactive, ref, watch } from "vue";
|
|
23
23
|
import { formatDateTime } from "@jskit-ai/kernel/shared/support";
|
|
24
|
+
import { normalizeRecordId } from "@jskit-ai/kernel/shared/support/normalize";
|
|
24
25
|
import MembersAdminClientElement from "./MembersAdminClientElement.vue";
|
|
25
26
|
import { useCommand } from "../composables/useCommand.js";
|
|
26
27
|
import { useList } from "../composables/records/useList.js";
|
|
@@ -61,8 +62,8 @@ const collections = reactive({
|
|
|
61
62
|
const inviteFeedback = useUiFeedback();
|
|
62
63
|
const membersFeedback = useUiFeedback();
|
|
63
64
|
const teamFeedback = useUiFeedback();
|
|
64
|
-
const revokeInviteId = ref(
|
|
65
|
-
const removeMemberUserId = ref(
|
|
65
|
+
const revokeInviteId = ref("");
|
|
66
|
+
const removeMemberUserId = ref("");
|
|
66
67
|
|
|
67
68
|
const { route, currentSurfaceId, workspaceSlugFromRoute, mergePlacementContext } =
|
|
68
69
|
useWorkspaceRouteContext();
|
|
@@ -83,7 +84,8 @@ const workspaceInvitesApiPath = computed(() =>
|
|
|
83
84
|
);
|
|
84
85
|
|
|
85
86
|
function workspaceMembersPath(memberId) {
|
|
86
|
-
|
|
87
|
+
const normalizedMemberId = encodeURIComponent(String(memberId || "").trim());
|
|
88
|
+
return `${workspaceMembersApiPath.value}/${normalizedMemberId}`;
|
|
87
89
|
}
|
|
88
90
|
|
|
89
91
|
function workspaceInvitePath(inviteId) {
|
|
@@ -145,8 +147,8 @@ function resetViewState() {
|
|
|
145
147
|
collections.members = [];
|
|
146
148
|
collections.invites = [];
|
|
147
149
|
clearRoleOptions();
|
|
148
|
-
revokeInviteId.value =
|
|
149
|
-
removeMemberUserId.value =
|
|
150
|
+
revokeInviteId.value = "";
|
|
151
|
+
removeMemberUserId.value = "";
|
|
150
152
|
}
|
|
151
153
|
|
|
152
154
|
function toRoleTitle(roleSid) {
|
|
@@ -223,7 +225,7 @@ function normalizeMembers(entries) {
|
|
|
223
225
|
return source.map((entry) => {
|
|
224
226
|
const value = entry && typeof entry === "object" ? entry : {};
|
|
225
227
|
return {
|
|
226
|
-
userId:
|
|
228
|
+
userId: normalizeRecordId(value.userId, { fallback: "" }),
|
|
227
229
|
roleSid: String(value.roleSid || "").trim().toLowerCase(),
|
|
228
230
|
status: String(value.status || "").trim().toLowerCase(),
|
|
229
231
|
displayName: String(value.displayName || "").trim(),
|
|
@@ -238,12 +240,12 @@ function normalizeInvites(entries) {
|
|
|
238
240
|
return source.map((entry) => {
|
|
239
241
|
const value = entry && typeof entry === "object" ? entry : {};
|
|
240
242
|
return {
|
|
241
|
-
id:
|
|
243
|
+
id: normalizeRecordId(value.id, { fallback: "" }),
|
|
242
244
|
email: String(value.email || "").trim().toLowerCase(),
|
|
243
245
|
roleSid: String(value.roleSid || "").trim().toLowerCase(),
|
|
244
246
|
status: String(value.status || "").trim().toLowerCase(),
|
|
245
247
|
expiresAt: value.expiresAt || "",
|
|
246
|
-
invitedByUserId: value.invitedByUserId
|
|
248
|
+
invitedByUserId: normalizeRecordId(value.invitedByUserId, { fallback: null })
|
|
247
249
|
};
|
|
248
250
|
});
|
|
249
251
|
}
|
|
@@ -576,7 +578,7 @@ async function submitRevokeInvite(inviteId) {
|
|
|
576
578
|
return;
|
|
577
579
|
}
|
|
578
580
|
|
|
579
|
-
revokeInviteId.value =
|
|
581
|
+
revokeInviteId.value = normalizeRecordId(inviteId, { fallback: "" });
|
|
580
582
|
teamFeedback.clear();
|
|
581
583
|
|
|
582
584
|
try {
|
|
@@ -591,7 +593,7 @@ async function submitRevokeInvite(inviteId) {
|
|
|
591
593
|
} catch (error) {
|
|
592
594
|
teamFeedback.error(error, "Unable to revoke invite.");
|
|
593
595
|
} finally {
|
|
594
|
-
revokeInviteId.value =
|
|
596
|
+
revokeInviteId.value = "";
|
|
595
597
|
}
|
|
596
598
|
}
|
|
597
599
|
|
|
@@ -603,8 +605,8 @@ async function submitMemberRoleUpdate(member, roleSid) {
|
|
|
603
605
|
membersFeedback.clear();
|
|
604
606
|
|
|
605
607
|
try {
|
|
606
|
-
const memberUserId =
|
|
607
|
-
if (!
|
|
608
|
+
const memberUserId = normalizeRecordId(member?.userId, { fallback: null });
|
|
609
|
+
if (!memberUserId) {
|
|
608
610
|
throw new Error("Member user id is invalid.");
|
|
609
611
|
}
|
|
610
612
|
|
|
@@ -630,12 +632,12 @@ async function submitRemoveMember(member) {
|
|
|
630
632
|
membersFeedback.clear();
|
|
631
633
|
|
|
632
634
|
try {
|
|
633
|
-
const memberUserId =
|
|
634
|
-
if (!
|
|
635
|
+
const memberUserId = normalizeRecordId(member?.userId, { fallback: null });
|
|
636
|
+
if (!memberUserId) {
|
|
635
637
|
throw new Error("Member user id is invalid.");
|
|
636
638
|
}
|
|
637
639
|
|
|
638
|
-
removeMemberUserId.value = memberUserId;
|
|
640
|
+
removeMemberUserId.value = normalizeRecordId(memberUserId, { fallback: "" });
|
|
639
641
|
await memberRemoveCommand.run({
|
|
640
642
|
memberUserId
|
|
641
643
|
});
|
|
@@ -647,7 +649,7 @@ async function submitRemoveMember(member) {
|
|
|
647
649
|
} catch (error) {
|
|
648
650
|
membersFeedback.error(error, "Unable to remove member.");
|
|
649
651
|
} finally {
|
|
650
|
-
removeMemberUserId.value =
|
|
652
|
+
removeMemberUserId.value = "";
|
|
651
653
|
}
|
|
652
654
|
}
|
|
653
655
|
</script>
|
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
normalizeReturnToPath as normalizeSharedReturnToPath,
|
|
4
4
|
resolveAllowedOriginsFromPlacementContext
|
|
5
5
|
} from "@jskit-ai/kernel/shared/support";
|
|
6
|
+
import { normalizeRecordId } from "@jskit-ai/kernel/shared/support/normalize";
|
|
6
7
|
import { normalizeRecord } from "../../support/runtimeNormalization.js";
|
|
7
8
|
|
|
8
9
|
function normalizeReturnToPath(value, { fallback = "/", accountSettingsPath = "/account", allowedOrigins = [] } = {}) {
|
|
@@ -27,9 +28,9 @@ function normalizePendingInvite(entry) {
|
|
|
27
28
|
return null;
|
|
28
29
|
}
|
|
29
30
|
|
|
30
|
-
const id =
|
|
31
|
-
const workspaceId =
|
|
32
|
-
if (!
|
|
31
|
+
const id = normalizeRecordId(entry.id, { fallback: null });
|
|
32
|
+
const workspaceId = normalizeRecordId(entry.workspaceId, { fallback: null });
|
|
33
|
+
if (!id || !workspaceId) {
|
|
33
34
|
return null;
|
|
34
35
|
}
|
|
35
36
|
|
package/src/client/index.js
CHANGED
|
@@ -2,8 +2,6 @@ import { UsersWebClientProvider } from "./providers/UsersWebClientProvider.js";
|
|
|
2
2
|
|
|
3
3
|
export { UsersWebClientProvider } from "./providers/UsersWebClientProvider.js";
|
|
4
4
|
export { UsersWorkspacesClientProvider } from "./providers/UsersWorkspacesClientProvider.js";
|
|
5
|
-
export { default as ConsoleSettingsClientElement } from "./components/ConsoleSettingsClientElement.vue";
|
|
6
|
-
export { default as WorkspaceSettingsClientElement } from "./components/WorkspaceSettingsClientElement.vue";
|
|
7
5
|
|
|
8
6
|
const clientProviders = Object.freeze([UsersWebClientProvider]);
|
|
9
7
|
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { normalizeRecordId } from "@jskit-ai/kernel/shared/support/normalize";
|
|
2
|
+
|
|
1
3
|
function buildBootstrapApiPath(workspaceSlug = "") {
|
|
2
4
|
const normalizedWorkspaceSlug = String(workspaceSlug || "").trim();
|
|
3
5
|
if (!normalizedWorkspaceSlug) {
|
|
@@ -15,9 +17,9 @@ function normalizeWorkspaceEntry(entry) {
|
|
|
15
17
|
return null;
|
|
16
18
|
}
|
|
17
19
|
|
|
18
|
-
const id =
|
|
20
|
+
const id = normalizeRecordId(entry.id, { fallback: null });
|
|
19
21
|
const slug = String(entry.slug || "").trim();
|
|
20
|
-
if (!
|
|
22
|
+
if (!id || !slug) {
|
|
21
23
|
return null;
|
|
22
24
|
}
|
|
23
25
|
|
|
@@ -69,8 +71,8 @@ function resolvePlacementUserFromBootstrapPayload(payload = {}, currentUser = nu
|
|
|
69
71
|
const fallbackUser = currentUser && typeof currentUser === "object" ? currentUser : {};
|
|
70
72
|
const nextUser = {};
|
|
71
73
|
|
|
72
|
-
const userId =
|
|
73
|
-
if (
|
|
74
|
+
const userId = normalizeRecordId(session.userId || fallbackUser.id, { fallback: null });
|
|
75
|
+
if (userId) {
|
|
74
76
|
nextUser.id = userId;
|
|
75
77
|
}
|
|
76
78
|
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import UsersShellMenuLinkItem from "../components/UsersShellMenuLinkItem.vue";
|
|
2
|
-
import UsersSurfaceAwareMenuLinkItem from "../components/UsersSurfaceAwareMenuLinkItem.vue";
|
|
3
1
|
import UsersProfileSurfaceSwitchMenuItem from "../components/UsersProfileSurfaceSwitchMenuItem.vue";
|
|
2
|
+
import UsersHomeToolsWidget from "../components/UsersHomeToolsWidget.vue";
|
|
4
3
|
import ProfileClientElement from "../components/ProfileClientElement.vue";
|
|
5
4
|
import {
|
|
6
5
|
createBootstrapPlacementRuntime
|
|
@@ -15,9 +14,8 @@ class UsersWebClientProvider {
|
|
|
15
14
|
throw new Error("UsersWebClientProvider requires application singleton().");
|
|
16
15
|
}
|
|
17
16
|
|
|
18
|
-
app.singleton("users.web.shell.menu-link-item", () => UsersShellMenuLinkItem);
|
|
19
|
-
app.singleton("users.web.shell.surface-aware-menu-link-item", () => UsersSurfaceAwareMenuLinkItem);
|
|
20
17
|
app.singleton("users.web.profile.menu.surface-switch-item", () => UsersProfileSurfaceSwitchMenuItem);
|
|
18
|
+
app.singleton("users.web.home.tools.widget", () => UsersHomeToolsWidget);
|
|
21
19
|
app.singleton("users.web.profile.element", () => ProfileClientElement);
|
|
22
20
|
app.singleton("users.web.bootstrap-placement.runtime", (scope) => createBootstrapPlacementRuntime({ app: scope }));
|
|
23
21
|
}
|
|
@@ -3,7 +3,6 @@ import UsersWorkspaceToolsWidget from "../components/UsersWorkspaceToolsWidget.v
|
|
|
3
3
|
import UsersWorkspaceSettingsMenuItem from "../components/UsersWorkspaceSettingsMenuItem.vue";
|
|
4
4
|
import UsersWorkspaceMembersMenuItem from "../components/UsersWorkspaceMembersMenuItem.vue";
|
|
5
5
|
import MembersAdminClientElement from "../components/MembersAdminClientElement.vue";
|
|
6
|
-
import WorkspaceSettingsClientElement from "../components/WorkspaceSettingsClientElement.vue";
|
|
7
6
|
|
|
8
7
|
class UsersWorkspacesClientProvider {
|
|
9
8
|
static id = "workspaces.web.client";
|
|
@@ -19,7 +18,6 @@ class UsersWorkspacesClientProvider {
|
|
|
19
18
|
app.singleton("users.web.workspace-settings.menu-item", () => UsersWorkspaceSettingsMenuItem);
|
|
20
19
|
app.singleton("users.web.workspace-members.menu-item", () => UsersWorkspaceMembersMenuItem);
|
|
21
20
|
app.singleton("users.web.members-admin.element", () => MembersAdminClientElement);
|
|
22
|
-
app.singleton("users.web.workspace-settings.element", () => WorkspaceSettingsClientElement);
|
|
23
21
|
}
|
|
24
22
|
}
|
|
25
23
|
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
const DEFAULT_TOOLS_LINK_COMPONENT_TOKEN = "local.main.ui.surface-aware-menu-link-item";
|
|
2
|
+
|
|
3
|
+
const HOME_TOOLS_OUTLET = Object.freeze({
|
|
4
|
+
target: "home-tools:primary-menu",
|
|
5
|
+
defaultLinkComponentToken: DEFAULT_TOOLS_LINK_COMPONENT_TOKEN,
|
|
6
|
+
ariaLabel: "Home tools"
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
const WORKSPACE_TOOLS_OUTLET = Object.freeze({
|
|
10
|
+
target: "workspace-tools:primary-menu",
|
|
11
|
+
defaultLinkComponentToken: DEFAULT_TOOLS_LINK_COMPONENT_TOKEN,
|
|
12
|
+
ariaLabel: "Workspace tools"
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
export {
|
|
16
|
+
DEFAULT_TOOLS_LINK_COMPONENT_TOKEN,
|
|
17
|
+
HOME_TOOLS_OUTLET,
|
|
18
|
+
WORKSPACE_TOOLS_OUTLET
|
|
19
|
+
};
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
<script setup>
|
|
2
|
-
|
|
2
|
+
// To redirect this settings shell to a child page, uncomment and edit the example below.
|
|
3
|
+
// definePage({
|
|
4
|
+
// redirect: (to) => `${to.path}/your_child_segment`
|
|
5
|
+
// });
|
|
3
6
|
</script>
|
|
4
7
|
|
|
5
|
-
<template
|
|
6
|
-
<ConsoleSettingsClientElement />
|
|
7
|
-
</template>
|
|
8
|
+
<template />
|
|
@@ -15,7 +15,10 @@ import { RouterView } from "vue-router";
|
|
|
15
15
|
<v-row no-gutters>
|
|
16
16
|
<v-col cols="12" md="3" lg="2" class="pr-md-4 mb-4 mb-md-0">
|
|
17
17
|
<v-list nav density="comfortable" rounded="lg" border>
|
|
18
|
-
<ShellOutlet
|
|
18
|
+
<ShellOutlet
|
|
19
|
+
target="console-settings:primary-menu"
|
|
20
|
+
default-link-component-token="local.main.ui.surface-aware-menu-link-item"
|
|
21
|
+
/>
|
|
19
22
|
</v-list>
|
|
20
23
|
</v-col>
|
|
21
24
|
|
package/test/bootstrap.test.js
CHANGED
|
@@ -17,7 +17,7 @@ test("resolvePlacementUserFromBootstrapPayload maps profile fields used by place
|
|
|
17
17
|
const user = resolvePlacementUserFromBootstrapPayload({
|
|
18
18
|
session: {
|
|
19
19
|
authenticated: true,
|
|
20
|
-
userId: 42
|
|
20
|
+
userId: "42"
|
|
21
21
|
},
|
|
22
22
|
profile: {
|
|
23
23
|
displayName: "Ada Lovelace",
|
|
@@ -29,7 +29,7 @@ test("resolvePlacementUserFromBootstrapPayload maps profile fields used by place
|
|
|
29
29
|
});
|
|
30
30
|
|
|
31
31
|
assert.deepEqual(user, {
|
|
32
|
-
id: 42,
|
|
32
|
+
id: "42",
|
|
33
33
|
displayName: "Ada Lovelace",
|
|
34
34
|
name: "Ada Lovelace",
|
|
35
35
|
email: "ada@example.com",
|
|
@@ -245,7 +245,7 @@ test("bootstrap placement runtime writes user/workspace/permissions into placeme
|
|
|
245
245
|
return {
|
|
246
246
|
session: {
|
|
247
247
|
authenticated: true,
|
|
248
|
-
userId: 7
|
|
248
|
+
userId: "7"
|
|
249
249
|
},
|
|
250
250
|
profile: {
|
|
251
251
|
displayName: "Ada Lovelace",
|
|
@@ -260,10 +260,10 @@ test("bootstrap placement runtime writes user/workspace/permissions into placeme
|
|
|
260
260
|
}
|
|
261
261
|
},
|
|
262
262
|
pendingInvites: [
|
|
263
|
-
{ id: 1, workspaceId: 1, token: "a" },
|
|
264
|
-
{ id: 2, workspaceId: 2, token: "b" }
|
|
263
|
+
{ id: "1", workspaceId: "1", token: "a" },
|
|
264
|
+
{ id: "2", workspaceId: "2", token: "b" }
|
|
265
265
|
],
|
|
266
|
-
workspaces: [{ id: 1, slug: "acme", name: "Acme Workspace" }],
|
|
266
|
+
workspaces: [{ id: "1", slug: "acme", name: "Acme Workspace" }],
|
|
267
267
|
permissions: ["workspace.settings.view"]
|
|
268
268
|
};
|
|
269
269
|
}
|
|
@@ -280,7 +280,7 @@ test("bootstrap placement runtime writes user/workspace/permissions into placeme
|
|
|
280
280
|
assert.equal(runtime.getWorkspaceBootstrapStatus("acme"), WORKSPACE_BOOTSTRAP_STATUS_RESOLVED);
|
|
281
281
|
assert.equal(context.workspaceBootstrapStatuses?.acme, WORKSPACE_BOOTSTRAP_STATUS_RESOLVED);
|
|
282
282
|
assert.deepEqual(context.user, {
|
|
283
|
-
id: 7,
|
|
283
|
+
id: "7",
|
|
284
284
|
displayName: "Ada Lovelace",
|
|
285
285
|
name: "Ada Lovelace",
|
|
286
286
|
email: "ada@example.com",
|
|
@@ -305,7 +305,7 @@ test("bootstrap placement runtime resolves workspace slug from pathname when sur
|
|
|
305
305
|
return {
|
|
306
306
|
session: {
|
|
307
307
|
authenticated: true,
|
|
308
|
-
userId: 1
|
|
308
|
+
userId: "1"
|
|
309
309
|
},
|
|
310
310
|
profile: {
|
|
311
311
|
displayName: "User",
|
|
@@ -314,7 +314,7 @@ test("bootstrap placement runtime resolves workspace slug from pathname when sur
|
|
|
314
314
|
effectiveUrl: ""
|
|
315
315
|
}
|
|
316
316
|
},
|
|
317
|
-
workspaces: [{ id: 1, slug: "acme", name: "Acme Workspace" }],
|
|
317
|
+
workspaces: [{ id: "1", slug: "acme", name: "Acme Workspace" }],
|
|
318
318
|
permissions: ["workspace.settings.view"]
|
|
319
319
|
};
|
|
320
320
|
}
|
|
@@ -348,7 +348,7 @@ test("bootstrap placement runtime does not mutate placement auth context", async
|
|
|
348
348
|
return {
|
|
349
349
|
session: {
|
|
350
350
|
authenticated: true,
|
|
351
|
-
userId: 9
|
|
351
|
+
userId: "9"
|
|
352
352
|
},
|
|
353
353
|
profile: {
|
|
354
354
|
displayName: "User",
|
|
@@ -357,7 +357,7 @@ test("bootstrap placement runtime does not mutate placement auth context", async
|
|
|
357
357
|
effectiveUrl: ""
|
|
358
358
|
}
|
|
359
359
|
},
|
|
360
|
-
workspaces: [{ id: 1, slug: "acme", name: "Workspace" }],
|
|
360
|
+
workspaces: [{ id: "1", slug: "acme", name: "Workspace" }],
|
|
361
361
|
permissions: []
|
|
362
362
|
};
|
|
363
363
|
}
|
|
@@ -387,7 +387,7 @@ test("bootstrap placement runtime refetches on route changes and users.bootstrap
|
|
|
387
387
|
return {
|
|
388
388
|
session: {
|
|
389
389
|
authenticated: true,
|
|
390
|
-
userId: 1
|
|
390
|
+
userId: "1"
|
|
391
391
|
},
|
|
392
392
|
profile: {
|
|
393
393
|
displayName: "User",
|
|
@@ -396,7 +396,7 @@ test("bootstrap placement runtime refetches on route changes and users.bootstrap
|
|
|
396
396
|
effectiveUrl: ""
|
|
397
397
|
}
|
|
398
398
|
},
|
|
399
|
-
workspaces: [{ id: 1, slug: workspaceSlug || "acme", name: "Workspace" }],
|
|
399
|
+
workspaces: [{ id: "1", slug: workspaceSlug || "acme", name: "Workspace" }],
|
|
400
400
|
permissions: []
|
|
401
401
|
};
|
|
402
402
|
}
|
|
@@ -436,7 +436,7 @@ test("bootstrap placement runtime refetches when auth context changes", async ()
|
|
|
436
436
|
return {
|
|
437
437
|
session: {
|
|
438
438
|
authenticated: true,
|
|
439
|
-
userId: 1
|
|
439
|
+
userId: "1"
|
|
440
440
|
},
|
|
441
441
|
profile: {
|
|
442
442
|
displayName: "User",
|
|
@@ -445,7 +445,7 @@ test("bootstrap placement runtime refetches when auth context changes", async ()
|
|
|
445
445
|
effectiveUrl: ""
|
|
446
446
|
}
|
|
447
447
|
},
|
|
448
|
-
workspaces: [{ id: 1, slug: workspaceSlug || "acme", name: "Workspace" }],
|
|
448
|
+
workspaces: [{ id: "1", slug: workspaceSlug || "acme", name: "Workspace" }],
|
|
449
449
|
permissions: []
|
|
450
450
|
};
|
|
451
451
|
}
|
|
@@ -535,7 +535,7 @@ test("bootstrap placement runtime reapplies theme when bootstrap payload changes
|
|
|
535
535
|
return {
|
|
536
536
|
session: {
|
|
537
537
|
authenticated: true,
|
|
538
|
-
userId: 1
|
|
538
|
+
userId: "1"
|
|
539
539
|
},
|
|
540
540
|
profile: {
|
|
541
541
|
displayName: "User",
|
|
@@ -547,7 +547,7 @@ test("bootstrap placement runtime reapplies theme when bootstrap payload changes
|
|
|
547
547
|
userSettings: {
|
|
548
548
|
theme: fetchCount === 1 ? "dark" : "light"
|
|
549
549
|
},
|
|
550
|
-
workspaces: [{ id: 1, slug: workspaceSlug || "acme", name: "Workspace" }],
|
|
550
|
+
workspaces: [{ id: "1", slug: workspaceSlug || "acme", name: "Workspace" }],
|
|
551
551
|
permissions: []
|
|
552
552
|
};
|
|
553
553
|
}
|
|
@@ -575,7 +575,7 @@ test("bootstrap placement runtime applies workspace palette via Vuetify workspac
|
|
|
575
575
|
return {
|
|
576
576
|
session: {
|
|
577
577
|
authenticated: true,
|
|
578
|
-
userId: 1
|
|
578
|
+
userId: "1"
|
|
579
579
|
},
|
|
580
580
|
workspaceSettings: {
|
|
581
581
|
lightPrimaryColor: "#CC3344",
|
|
@@ -589,7 +589,7 @@ test("bootstrap placement runtime applies workspace palette via Vuetify workspac
|
|
|
589
589
|
},
|
|
590
590
|
workspaces: [
|
|
591
591
|
{
|
|
592
|
-
id: 1,
|
|
592
|
+
id: "1",
|
|
593
593
|
slug: "acme",
|
|
594
594
|
name: "Acme Workspace"
|
|
595
595
|
}
|
|
@@ -630,8 +630,8 @@ test("bootstrap placement runtime marks workspace slug as not_found and clears w
|
|
|
630
630
|
const placementRuntime = createPlacementRuntimeStub();
|
|
631
631
|
placementRuntime.setContext(
|
|
632
632
|
{
|
|
633
|
-
workspace: { id: 1, slug: "acme", name: "Acme Workspace" },
|
|
634
|
-
workspaces: [{ id: 1, slug: "acme", name: "Acme Workspace" }],
|
|
633
|
+
workspace: { id: "1", slug: "acme", name: "Acme Workspace" },
|
|
634
|
+
workspaces: [{ id: "1", slug: "acme", name: "Acme Workspace" }],
|
|
635
635
|
permissions: ["workspace.settings.view"]
|
|
636
636
|
},
|
|
637
637
|
{ source: "test.seed" }
|
|
@@ -680,7 +680,7 @@ test("bootstrap placement runtime updates status per workspace slug across route
|
|
|
680
680
|
return {
|
|
681
681
|
session: {
|
|
682
682
|
authenticated: true,
|
|
683
|
-
userId: 1
|
|
683
|
+
userId: "1"
|
|
684
684
|
},
|
|
685
685
|
profile: {
|
|
686
686
|
displayName: "User",
|
|
@@ -689,7 +689,7 @@ test("bootstrap placement runtime updates status per workspace slug across route
|
|
|
689
689
|
effectiveUrl: ""
|
|
690
690
|
}
|
|
691
691
|
},
|
|
692
|
-
workspaces: [{ id: 1, slug: workspaceSlug || "acme", name: "Workspace" }],
|
|
692
|
+
workspaces: [{ id: "1", slug: workspaceSlug || "acme", name: "Workspace" }],
|
|
693
693
|
permissions: []
|
|
694
694
|
};
|
|
695
695
|
}
|
|
@@ -721,7 +721,7 @@ test("bootstrap placement runtime uses requestedWorkspace status and keeps globa
|
|
|
721
721
|
return {
|
|
722
722
|
session: {
|
|
723
723
|
authenticated: true,
|
|
724
|
-
userId: 4
|
|
724
|
+
userId: "4"
|
|
725
725
|
},
|
|
726
726
|
profile: {
|
|
727
727
|
displayName: "Chiara",
|
|
@@ -730,7 +730,7 @@ test("bootstrap placement runtime uses requestedWorkspace status and keeps globa
|
|
|
730
730
|
effectiveUrl: ""
|
|
731
731
|
}
|
|
732
732
|
},
|
|
733
|
-
workspaces: [{ id: 3, slug: "chiaramobily", name: "Chiara Workspace" }],
|
|
733
|
+
workspaces: [{ id: "3", slug: "chiaramobily", name: "Chiara Workspace" }],
|
|
734
734
|
requestedWorkspace: {
|
|
735
735
|
slug: "tonymobily",
|
|
736
736
|
status: "forbidden"
|
|
@@ -763,7 +763,7 @@ test("bootstrap placement runtime uses requestedWorkspace=not_found without forc
|
|
|
763
763
|
return {
|
|
764
764
|
session: {
|
|
765
765
|
authenticated: true,
|
|
766
|
-
userId: 1
|
|
766
|
+
userId: "1"
|
|
767
767
|
},
|
|
768
768
|
profile: {
|
|
769
769
|
displayName: "User",
|
|
@@ -772,7 +772,7 @@ test("bootstrap placement runtime uses requestedWorkspace=not_found without forc
|
|
|
772
772
|
effectiveUrl: ""
|
|
773
773
|
}
|
|
774
774
|
},
|
|
775
|
-
workspaces: [{ id: 1, slug: "acme", name: "Acme Workspace" }],
|
|
775
|
+
workspaces: [{ id: "1", slug: "acme", name: "Acme Workspace" }],
|
|
776
776
|
requestedWorkspace: {
|
|
777
777
|
slug: "missing",
|
|
778
778
|
status: "not_found"
|
|
@@ -811,9 +811,9 @@ test("bootstrap placement runtime guard wrapper preserves delegated deny outcome
|
|
|
811
811
|
return {
|
|
812
812
|
session: {
|
|
813
813
|
authenticated: true,
|
|
814
|
-
userId: 1
|
|
814
|
+
userId: "1"
|
|
815
815
|
},
|
|
816
|
-
workspaces: [{ id: 1, slug: "acme", name: "Acme" }],
|
|
816
|
+
workspaces: [{ id: "1", slug: "acme", name: "Acme" }],
|
|
817
817
|
permissions: []
|
|
818
818
|
};
|
|
819
819
|
}
|
|
@@ -854,7 +854,7 @@ test("bootstrap placement runtime guard wrapper blocks forbidden workspace route
|
|
|
854
854
|
return {
|
|
855
855
|
session: {
|
|
856
856
|
authenticated: true,
|
|
857
|
-
userId: 1
|
|
857
|
+
userId: "1"
|
|
858
858
|
},
|
|
859
859
|
workspaces: [],
|
|
860
860
|
permissions: []
|
|
@@ -1032,7 +1032,7 @@ test("bootstrap placement runtime enforces surface access policies after bootstr
|
|
|
1032
1032
|
return {
|
|
1033
1033
|
session: {
|
|
1034
1034
|
authenticated: true,
|
|
1035
|
-
userId: 1
|
|
1035
|
+
userId: "1"
|
|
1036
1036
|
},
|
|
1037
1037
|
workspaces: [],
|
|
1038
1038
|
permissions: [],
|
|
@@ -1059,9 +1059,9 @@ test("bootstrap placement runtime captures guard evaluator assignments after ini
|
|
|
1059
1059
|
return {
|
|
1060
1060
|
session: {
|
|
1061
1061
|
authenticated: true,
|
|
1062
|
-
userId: 1
|
|
1062
|
+
userId: "1"
|
|
1063
1063
|
},
|
|
1064
|
-
workspaces: [{ id: 1, slug: "acme", name: "Acme" }],
|
|
1064
|
+
workspaces: [{ id: "1", slug: "acme", name: "Acme" }],
|
|
1065
1065
|
permissions: []
|
|
1066
1066
|
};
|
|
1067
1067
|
}
|
|
@@ -8,30 +8,217 @@ import descriptor from "../package.descriptor.mjs";
|
|
|
8
8
|
const TEST_DIRECTORY = path.dirname(fileURLToPath(import.meta.url));
|
|
9
9
|
const PACKAGE_DIR = path.resolve(TEST_DIRECTORY, "..");
|
|
10
10
|
|
|
11
|
-
function
|
|
11
|
+
function readOutlets(host = "") {
|
|
12
12
|
const outlets = descriptor?.metadata?.ui?.placements?.outlets;
|
|
13
|
+
const normalizedTarget = String(host || "").trim();
|
|
13
14
|
return Array.isArray(outlets)
|
|
14
|
-
? outlets.filter((entry) => String(entry?.
|
|
15
|
+
? outlets.filter((entry) => String(entry?.target || "").trim() === normalizedTarget)
|
|
15
16
|
: [];
|
|
16
17
|
}
|
|
17
18
|
|
|
19
|
+
function findContribution(id) {
|
|
20
|
+
const contributions = descriptor?.metadata?.ui?.placements?.contributions;
|
|
21
|
+
return Array.isArray(contributions)
|
|
22
|
+
? contributions.find((entry) => String(entry?.id || "").trim() === id) || null
|
|
23
|
+
: null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function findTextMutation(id) {
|
|
27
|
+
const textMutations = descriptor?.mutations?.text;
|
|
28
|
+
return Array.isArray(textMutations)
|
|
29
|
+
? textMutations.find((entry) => String(entry?.id || "").trim() === id) || null
|
|
30
|
+
: null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function findFileMutation(id) {
|
|
34
|
+
const fileMutations = descriptor?.mutations?.files;
|
|
35
|
+
return Array.isArray(fileMutations)
|
|
36
|
+
? fileMutations.find((entry) => String(entry?.id || "").trim() === id) || null
|
|
37
|
+
: null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function expectContribution(id, expected = {}) {
|
|
41
|
+
const contribution = findContribution(id);
|
|
42
|
+
assert.ok(contribution, `Expected contribution "${id}".`);
|
|
43
|
+
|
|
44
|
+
for (const [key, value] of Object.entries(expected)) {
|
|
45
|
+
assert.deepEqual(contribution[key], value);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function expectTextMutation(id, { reason = "", category = "", skipIfContains = "", snippets = [] } = {}) {
|
|
50
|
+
const mutation = findTextMutation(id);
|
|
51
|
+
assert.ok(mutation, `Expected text mutation "${id}".`);
|
|
52
|
+
assert.equal(mutation.op, "append-text");
|
|
53
|
+
assert.equal(mutation.file, "src/placement.js");
|
|
54
|
+
assert.equal(mutation.position, "bottom");
|
|
55
|
+
assert.equal(mutation.id, id);
|
|
56
|
+
|
|
57
|
+
if (reason) {
|
|
58
|
+
assert.equal(mutation.reason, reason);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (category) {
|
|
62
|
+
assert.equal(mutation.category, category);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (skipIfContains) {
|
|
66
|
+
assert.equal(mutation.skipIfContains, skipIfContains);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
for (const snippet of snippets) {
|
|
70
|
+
assert.ok(mutation.value.includes(snippet), `Expected mutation "${id}" to include "${snippet}".`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
18
74
|
test("users-web console settings template exposes surface-derived settings outlets", async () => {
|
|
19
75
|
const source = await readFile(path.join(PACKAGE_DIR, "templates", "src", "pages", "console", "settings.vue"), "utf8");
|
|
20
76
|
|
|
21
|
-
assert.match(source,
|
|
77
|
+
assert.match(source, /target="console-settings:primary-menu"/);
|
|
78
|
+
assert.match(source, /default-link-component-token="local\.main\.ui\.surface-aware-menu-link-item"/);
|
|
79
|
+
assert.match(source, /<RouterView \/>/);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test("users-web console settings index template is a simple developer-owned stub", async () => {
|
|
83
|
+
const source = await readFile(path.join(PACKAGE_DIR, "templates", "src", "pages", "console", "settings", "index.vue"), "utf8");
|
|
84
|
+
|
|
85
|
+
assert.match(source, /definePage/);
|
|
86
|
+
assert.match(source, /your_child_segment/);
|
|
22
87
|
});
|
|
23
88
|
|
|
24
89
|
test("users-web descriptor metadata advertises console settings outlets with standard positions", () => {
|
|
25
|
-
const outlets =
|
|
90
|
+
const outlets = readOutlets("console-settings:primary-menu");
|
|
26
91
|
assert.deepEqual(
|
|
27
92
|
outlets,
|
|
28
93
|
[
|
|
29
94
|
{
|
|
30
|
-
|
|
31
|
-
|
|
95
|
+
target: "console-settings:primary-menu",
|
|
96
|
+
defaultLinkComponentToken: "local.main.ui.surface-aware-menu-link-item",
|
|
32
97
|
surfaces: ["console"],
|
|
33
98
|
source: "templates/src/pages/console/settings.vue"
|
|
34
99
|
}
|
|
35
100
|
]
|
|
36
101
|
);
|
|
102
|
+
assert.deepEqual(findFileMutation("users-web-page-console-settings"), {
|
|
103
|
+
from: "templates/src/pages/console/settings/index.vue",
|
|
104
|
+
toSurface: "console",
|
|
105
|
+
toSurfacePath: "settings/index.vue",
|
|
106
|
+
reason: "Install console settings index stub scaffold for app-owned landing or redirect behavior.",
|
|
107
|
+
category: "users-web",
|
|
108
|
+
id: "users-web-page-console-settings"
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
test("users-web home tools widget exposes home-tools outlet", async () => {
|
|
113
|
+
const source = await readFile(path.join(PACKAGE_DIR, "src", "client", "components", "UsersHomeToolsWidget.vue"), "utf8");
|
|
114
|
+
|
|
115
|
+
assert.match(source, /import \{ HOME_TOOLS_OUTLET \} from "\.\.\/\.\.\/shared\/toolsOutletContracts\.js";/);
|
|
116
|
+
assert.match(source, /<ShellOutletMenuWidget/);
|
|
117
|
+
assert.match(source, /:target="HOME_TOOLS_OUTLET\.target"/);
|
|
118
|
+
assert.match(source, /:default-link-component-token="HOME_TOOLS_OUTLET\.defaultLinkComponentToken"/);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
test("users-web workspace tools widget exposes workspace-tools outlet", async () => {
|
|
122
|
+
const source = await readFile(path.join(PACKAGE_DIR, "src", "client", "components", "UsersWorkspaceToolsWidget.vue"), "utf8");
|
|
123
|
+
|
|
124
|
+
assert.match(source, /import \{ WORKSPACE_TOOLS_OUTLET \} from "\.\.\/\.\.\/shared\/toolsOutletContracts\.js";/);
|
|
125
|
+
assert.match(source, /<ShellOutletMenuWidget/);
|
|
126
|
+
assert.match(source, /:target="WORKSPACE_TOOLS_OUTLET\.target"/);
|
|
127
|
+
assert.match(source, /:default-link-component-token="WORKSPACE_TOOLS_OUTLET\.defaultLinkComponentToken"/);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
test("users-web descriptor metadata advertises home tools outlet and standard home settings placements", () => {
|
|
131
|
+
assert.deepEqual(
|
|
132
|
+
readOutlets("home-tools:primary-menu"),
|
|
133
|
+
[
|
|
134
|
+
{
|
|
135
|
+
target: "home-tools:primary-menu",
|
|
136
|
+
defaultLinkComponentToken: "local.main.ui.surface-aware-menu-link-item",
|
|
137
|
+
surfaces: ["home"],
|
|
138
|
+
source: "src/client/components/UsersHomeToolsWidget.vue"
|
|
139
|
+
}
|
|
140
|
+
]
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
expectContribution("users.home.menu.home", {
|
|
144
|
+
target: "shell-layout:primary-menu",
|
|
145
|
+
surfaces: ["*"],
|
|
146
|
+
order: 50,
|
|
147
|
+
componentToken: "local.main.ui.surface-aware-menu-link-item",
|
|
148
|
+
when: "auth.authenticated === true",
|
|
149
|
+
source: "mutations.text#users-web-home-shell-menu-placement"
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
expectContribution("users.profile.menu.settings", {
|
|
153
|
+
target: "auth-profile-menu:primary-menu",
|
|
154
|
+
surfaces: ["*"],
|
|
155
|
+
order: 500,
|
|
156
|
+
componentToken: "auth.web.profile.menu.link-item",
|
|
157
|
+
when: "auth.authenticated === true",
|
|
158
|
+
source: "mutations.text#users-web-profile-settings-placement"
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
expectContribution("users.home.tools.widget", {
|
|
162
|
+
target: "shell-layout:top-right",
|
|
163
|
+
surfaces: ["home"],
|
|
164
|
+
order: 900,
|
|
165
|
+
componentToken: "users.web.home.tools.widget",
|
|
166
|
+
when: "auth.authenticated === true",
|
|
167
|
+
source: "mutations.text#users-web-home-tools-placement"
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
expectContribution("users.home.menu.settings", {
|
|
171
|
+
target: "home-tools:primary-menu",
|
|
172
|
+
surfaces: ["home"],
|
|
173
|
+
order: 100,
|
|
174
|
+
componentToken: "local.main.ui.surface-aware-menu-link-item",
|
|
175
|
+
when: "auth.authenticated === true",
|
|
176
|
+
source: "mutations.text#users-web-home-tools-placement"
|
|
177
|
+
});
|
|
178
|
+
assert.equal(findContribution("users.home.settings.general"), null);
|
|
179
|
+
|
|
180
|
+
expectTextMutation("users-web-home-tools-placement", {
|
|
181
|
+
reason: "Append users-web home tools widget and settings menu placements into app-owned placement registry.",
|
|
182
|
+
category: "users-web",
|
|
183
|
+
skipIfContains: 'id: "users.home.tools.widget"',
|
|
184
|
+
snippets: [
|
|
185
|
+
'id: "users.home.tools.widget"',
|
|
186
|
+
'componentToken: "users.web.home.tools.widget"',
|
|
187
|
+
'id: "users.home.menu.settings"',
|
|
188
|
+
'target: "home-tools:primary-menu"',
|
|
189
|
+
'componentToken: "local.main.ui.surface-aware-menu-link-item"',
|
|
190
|
+
'workspaceSuffix: "/settings"',
|
|
191
|
+
'nonWorkspaceSuffix: "/settings"'
|
|
192
|
+
]
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
expectTextMutation("users-web-profile-settings-placement", {
|
|
196
|
+
reason: "Append users-web profile settings menu placement into app-owned placement registry.",
|
|
197
|
+
category: "users-web",
|
|
198
|
+
skipIfContains: 'id: "users.profile.menu.settings"',
|
|
199
|
+
snippets: [
|
|
200
|
+
'id: "users.profile.menu.settings"',
|
|
201
|
+
'target: "auth-profile-menu:primary-menu"',
|
|
202
|
+
'componentToken: "auth.web.profile.menu.link-item"',
|
|
203
|
+
'label: "Settings"',
|
|
204
|
+
'to: "/account"'
|
|
205
|
+
]
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
expectTextMutation("users-web-home-shell-menu-placement", {
|
|
209
|
+
reason: "Append users-web home shell menu placement into app-owned placement registry.",
|
|
210
|
+
category: "users-web",
|
|
211
|
+
skipIfContains: 'id: "users.home.menu.home"',
|
|
212
|
+
snippets: [
|
|
213
|
+
'id: "users.home.menu.home"',
|
|
214
|
+
'target: "shell-layout:primary-menu"',
|
|
215
|
+
'componentToken: "local.main.ui.surface-aware-menu-link-item"',
|
|
216
|
+
'label: "Home"',
|
|
217
|
+
'surface: "home"',
|
|
218
|
+
'workspaceSuffix: "/"',
|
|
219
|
+
'nonWorkspaceSuffix: "/"',
|
|
220
|
+
'exact: true'
|
|
221
|
+
]
|
|
222
|
+
});
|
|
223
|
+
|
|
37
224
|
});
|
|
@@ -1,140 +0,0 @@
|
|
|
1
|
-
<script setup>
|
|
2
|
-
import { computed } from "vue";
|
|
3
|
-
import { useRoute } from "vue-router";
|
|
4
|
-
import { appendQueryString } from "@jskit-ai/kernel/shared/support";
|
|
5
|
-
import { isExternalLinkTarget, splitPathQueryHash } from "@jskit-ai/kernel/shared/support/linkPath";
|
|
6
|
-
import {
|
|
7
|
-
useWebPlacementContext,
|
|
8
|
-
resolveSurfaceNavigationTargetFromPlacementContext
|
|
9
|
-
} from "@jskit-ai/shell-web/client/placement";
|
|
10
|
-
import { resolveAccountSettingsPathFromPlacementContext } from "../lib/workspaceSurfacePaths.js";
|
|
11
|
-
import { resolveMenuLinkIcon } from "../lib/menuIcons.js";
|
|
12
|
-
|
|
13
|
-
const props = defineProps({
|
|
14
|
-
label: {
|
|
15
|
-
type: String,
|
|
16
|
-
default: ""
|
|
17
|
-
},
|
|
18
|
-
to: {
|
|
19
|
-
type: String,
|
|
20
|
-
default: ""
|
|
21
|
-
},
|
|
22
|
-
icon: {
|
|
23
|
-
type: String,
|
|
24
|
-
default: ""
|
|
25
|
-
},
|
|
26
|
-
disabled: {
|
|
27
|
-
type: Boolean,
|
|
28
|
-
default: false
|
|
29
|
-
}
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
const route = useRoute();
|
|
33
|
-
const { context: placementContext } = useWebPlacementContext();
|
|
34
|
-
|
|
35
|
-
function resolveFallbackReturnTo() {
|
|
36
|
-
if (typeof window !== "object" || !window || !window.location) {
|
|
37
|
-
return "/";
|
|
38
|
-
}
|
|
39
|
-
const pathname = String(window.location.pathname || "").trim() || "/";
|
|
40
|
-
const search = String(window.location.search || "").trim();
|
|
41
|
-
const hash = String(window.location.hash || "").trim();
|
|
42
|
-
return `${pathname}${search}${hash}`;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function resolveFallbackReturnToHref() {
|
|
46
|
-
if (typeof window !== "object" || !window || !window.location) {
|
|
47
|
-
return "/";
|
|
48
|
-
}
|
|
49
|
-
return String(window.location.href || "").trim() || resolveFallbackReturnTo();
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function resolvePathnameFromLinkTarget(target = "") {
|
|
53
|
-
const normalizedTarget = String(target || "").trim();
|
|
54
|
-
if (!normalizedTarget) {
|
|
55
|
-
return "";
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
if (isExternalLinkTarget(normalizedTarget)) {
|
|
59
|
-
try {
|
|
60
|
-
const parsed = new URL(normalizedTarget);
|
|
61
|
-
return String(parsed.pathname || "").trim();
|
|
62
|
-
} catch {
|
|
63
|
-
return "";
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
return splitPathQueryHash(normalizedTarget).pathname;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
const accountSettingsPathname = computed(() => {
|
|
71
|
-
const settingsPath = resolveAccountSettingsPathFromPlacementContext(placementContext.value);
|
|
72
|
-
return resolvePathnameFromLinkTarget(settingsPath);
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
const resolvedTo = computed(() => {
|
|
76
|
-
const target = String(props.to || "").trim();
|
|
77
|
-
if (!target) {
|
|
78
|
-
return "";
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
const targetPathname = resolvePathnameFromLinkTarget(target);
|
|
82
|
-
if (!targetPathname || targetPathname !== accountSettingsPathname.value) {
|
|
83
|
-
return target;
|
|
84
|
-
}
|
|
85
|
-
if (target.includes("returnTo=")) {
|
|
86
|
-
return target;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
const accountSettingsTarget = resolveSurfaceNavigationTargetFromPlacementContext(placementContext.value, {
|
|
90
|
-
path: target,
|
|
91
|
-
surfaceId: "account"
|
|
92
|
-
});
|
|
93
|
-
const routeFullPath = String(route?.fullPath || "").trim();
|
|
94
|
-
const routePath = String(route?.path || "").trim();
|
|
95
|
-
const returnTo = accountSettingsTarget.sameOrigin
|
|
96
|
-
? routeFullPath || routePath || resolveFallbackReturnTo()
|
|
97
|
-
: resolveFallbackReturnToHref();
|
|
98
|
-
const queryParams = new URLSearchParams({
|
|
99
|
-
returnTo
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
return appendQueryString(target, queryParams.toString());
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
const resolvedNavigationTarget = computed(() => {
|
|
106
|
-
const target = String(resolvedTo.value || "").trim();
|
|
107
|
-
if (!target) {
|
|
108
|
-
return {
|
|
109
|
-
href: "",
|
|
110
|
-
sameOrigin: true
|
|
111
|
-
};
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
const navigationTarget = resolveSurfaceNavigationTargetFromPlacementContext(placementContext.value, {
|
|
115
|
-
path: target
|
|
116
|
-
});
|
|
117
|
-
return {
|
|
118
|
-
href: navigationTarget.href,
|
|
119
|
-
sameOrigin: navigationTarget.sameOrigin
|
|
120
|
-
};
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
const resolvedIcon = computed(() =>
|
|
124
|
-
resolveMenuLinkIcon({
|
|
125
|
-
icon: props.icon,
|
|
126
|
-
label: props.label,
|
|
127
|
-
to: resolvedTo.value
|
|
128
|
-
})
|
|
129
|
-
);
|
|
130
|
-
</script>
|
|
131
|
-
|
|
132
|
-
<template>
|
|
133
|
-
<v-list-item
|
|
134
|
-
:title="props.label"
|
|
135
|
-
:to="resolvedNavigationTarget.sameOrigin ? resolvedNavigationTarget.href : undefined"
|
|
136
|
-
:href="resolvedNavigationTarget.sameOrigin ? undefined : resolvedNavigationTarget.href"
|
|
137
|
-
:prepend-icon="resolvedIcon || undefined"
|
|
138
|
-
:disabled="props.disabled"
|
|
139
|
-
/>
|
|
140
|
-
</template>
|
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
<script setup>
|
|
2
|
-
import { computed } from "vue";
|
|
3
|
-
import { useRoute } from "vue-router";
|
|
4
|
-
import { useWebPlacementContext } from "@jskit-ai/shell-web/client/placement";
|
|
5
|
-
import { usePaths } from "../composables/usePaths.js";
|
|
6
|
-
import { resolveMenuLinkIcon } from "../lib/menuIcons.js";
|
|
7
|
-
import { resolveMenuLinkTarget } from "../support/menuLinkTarget.js";
|
|
8
|
-
|
|
9
|
-
const props = defineProps({
|
|
10
|
-
label: {
|
|
11
|
-
type: String,
|
|
12
|
-
default: ""
|
|
13
|
-
},
|
|
14
|
-
to: {
|
|
15
|
-
type: String,
|
|
16
|
-
default: ""
|
|
17
|
-
},
|
|
18
|
-
icon: {
|
|
19
|
-
type: String,
|
|
20
|
-
default: ""
|
|
21
|
-
},
|
|
22
|
-
surface: {
|
|
23
|
-
type: String,
|
|
24
|
-
default: ""
|
|
25
|
-
},
|
|
26
|
-
workspaceSuffix: {
|
|
27
|
-
type: String,
|
|
28
|
-
default: "/"
|
|
29
|
-
},
|
|
30
|
-
nonWorkspaceSuffix: {
|
|
31
|
-
type: String,
|
|
32
|
-
default: "/"
|
|
33
|
-
},
|
|
34
|
-
disabled: {
|
|
35
|
-
type: Boolean,
|
|
36
|
-
default: false
|
|
37
|
-
}
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
const route = useRoute();
|
|
41
|
-
const paths = usePaths();
|
|
42
|
-
const { context: placementContext } = useWebPlacementContext();
|
|
43
|
-
|
|
44
|
-
const resolvedTo = computed(() => {
|
|
45
|
-
return resolveMenuLinkTarget({
|
|
46
|
-
to: props.to,
|
|
47
|
-
surface: props.surface,
|
|
48
|
-
currentSurfaceId: paths.currentSurfaceId.value,
|
|
49
|
-
placementContext: placementContext.value,
|
|
50
|
-
workspaceSuffix: props.workspaceSuffix,
|
|
51
|
-
nonWorkspaceSuffix: props.nonWorkspaceSuffix,
|
|
52
|
-
routeParams: route.params || {},
|
|
53
|
-
resolvePagePath(relativePath, options = {}) {
|
|
54
|
-
return paths.page(relativePath, options);
|
|
55
|
-
}
|
|
56
|
-
});
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
const resolvedIcon = computed(() =>
|
|
60
|
-
resolveMenuLinkIcon({
|
|
61
|
-
icon: props.icon,
|
|
62
|
-
label: props.label,
|
|
63
|
-
to: resolvedTo.value
|
|
64
|
-
})
|
|
65
|
-
);
|
|
66
|
-
</script>
|
|
67
|
-
|
|
68
|
-
<template>
|
|
69
|
-
<v-list-item
|
|
70
|
-
v-if="resolvedTo"
|
|
71
|
-
:title="props.label"
|
|
72
|
-
:to="resolvedTo"
|
|
73
|
-
:prepend-icon="resolvedIcon || undefined"
|
|
74
|
-
:disabled="props.disabled"
|
|
75
|
-
/>
|
|
76
|
-
</template>
|