@k2b/cloud 0.38.0 → 0.40.0
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 +8 -0
- package/package.json +12 -5
- package/scripts/build-canvas-workers.ts +109 -29
- package/scripts/build.ts +3 -5
- package/src/_internal/app-favicon.ts +4 -2
- package/src/_internal/app-path.ts +25 -0
- package/src/_internal/app-presentation.ts +4 -0
- package/src/_internal/canvas-worker.ts +5 -3
- package/src/_internal/capabilities.ts +72 -48
- package/src/_internal/capability-reviews.ts +81 -0
- package/src/_internal/capability-schemas.ts +29 -0
- package/src/_internal/capability-sentences.ts +216 -1
- package/src/_internal/capability-streams.ts +7 -0
- package/src/_internal/define-app.ts +63 -16
- package/src/_internal/font-preloads.ts +2 -1
- package/src/_internal/panel-cookie.ts +39 -0
- package/src/_internal/registry-validation.ts +21 -26
- package/src/_internal/runtime-context.ts +1 -0
- package/src/_internal/runtime-watcher.ts +6 -0
- package/src/_internal/static-assets.ts +52 -3
- package/src/_internal/status-preserving-ssr.ts +2 -0
- package/src/access/PermissionEditor.browser-harness.tsx +57 -38
- package/src/access/PermissionEditor.tsx +32 -7
- package/src/access/ResourceApiKeys.tsx +5 -1
- package/src/account/EntitySearch.tsx +1 -1
- package/src/ai/action-review.ts +101 -0
- package/src/ai/approval-routes.ts +5 -2
- package/src/ai/approvals.ts +139 -28
- package/src/ai/background-review.ts +72 -0
- package/src/ai/browser.ts +10 -0
- package/src/ai/capabilities.ts +29 -11
- package/src/ai/capability-execution.ts +76 -43
- package/src/ai/capability-result.ts +95 -0
- package/src/ai/chat/action-card.ts +398 -0
- package/src/ai/chat/action-messages.ts +129 -0
- package/src/ai/chat/action-question.browser-harness.tsx +245 -0
- package/src/ai/chat/action-question.fixtures.ts +733 -0
- package/src/ai/chat/action-question.tsx +669 -0
- package/src/ai/chat/action-sentences.ts +1 -44
- package/src/ai/chat/blocks.tsx +19 -25
- package/src/ai/chat/message-actions.tsx +6 -0
- package/src/ai/chat/tool-action.tsx +481 -0
- package/src/ai/chat/turn-view.tsx +45 -163
- package/src/ai/chat/view-block.tsx +22 -7
- package/src/ai/chat-task-contracts.ts +31 -3
- package/src/ai/chat-task-reasons-migrate.ts +75 -0
- package/src/ai/chat-task-reasons.ts +92 -0
- package/src/ai/chat-tasks.ts +438 -78
- package/src/ai/client/controller.ts +59 -0
- package/src/ai/default-tools.ts +9 -1
- package/src/ai/executor.ts +190 -39
- package/src/ai/index.ts +14 -0
- package/src/ai/migrate.ts +39 -4
- package/src/ai/projects-routes.ts +3 -0
- package/src/ai/protocol.ts +14 -2
- package/src/ai/recent-resources.ts +240 -0
- package/src/ai/recent-sent-refs.ts +38 -0
- package/src/ai/request-context.ts +7 -1
- package/src/ai/routes.ts +172 -2
- package/src/ai/runtime.ts +13 -27
- package/src/ai/skills-routes.ts +3 -0
- package/src/ai/skills.ts +10 -1
- package/src/ai/store.ts +232 -27
- package/src/ai/timeline.ts +9 -1
- package/src/ai/tool-audit.ts +8 -0
- package/src/ai/tool-wording.ts +67 -0
- package/src/ai/turn-abort.ts +33 -0
- package/src/ai/types.ts +40 -4
- package/src/api/accounts-entities.ts +2 -1
- package/src/api/admin-ai-projects.ts +2 -0
- package/src/api/admin-ai-skills.ts +2 -0
- package/src/api/admin-ai-usage.ts +2 -0
- package/src/api/admin-core-settings.ts +190 -139
- package/src/api/admin-geoip.ts +80 -0
- package/src/api/admin-identity.ts +3 -0
- package/src/api/admin-lifecycle.ts +3 -0
- package/src/api/admin-outgoing-mail.ts +6 -2
- package/src/api/app-approval.ts +54 -6
- package/src/api/approvals-loader.ts +23 -1
- package/src/api/approvals.ts +3 -1
- package/src/api/auth.ts +25 -6
- package/src/api/capabilities.ts +44 -11
- package/src/api/capability-streams.ts +12 -0
- package/src/api/help.ts +4 -28
- package/src/api/index.ts +7 -1
- package/src/api/lasting-access-messages.ts +17 -0
- package/src/api/mcp.ts +3 -0
- package/src/api/me-panel-consent.ts +71 -0
- package/src/api/me.ts +80 -33
- package/src/api/search.ts +3 -1
- package/src/api/sync-ops.ts +2 -0
- package/src/api/widgets.ts +3 -0
- package/src/approvals/Approvals.tsx +180 -238
- package/src/approvals/messages.ts +12 -24
- package/src/browser/CloudResourceSearch.browser-harness.tsx +10 -2
- package/src/browser/CloudResourceSearch.tsx +10 -6
- package/src/browser/app-approval.ts +60 -6
- package/src/browser/commands.ts +22 -6
- package/src/browser/composer-catalog.ts +145 -0
- package/src/browser/composer-insert-messages.ts +31 -0
- package/src/browser/composer-insert.ts +160 -0
- package/src/browser/composer-open-messages.ts +17 -0
- package/src/browser/composer-open.ts +110 -0
- package/src/browser/discoverable-command.ts +6 -0
- package/src/browser/hints.ts +6 -0
- package/src/browser/live-connection.ts +26 -2
- package/src/browser/navigation-apps.ts +7 -0
- package/src/browser/panel-context-bridge.ts +30 -0
- package/src/browser/panel-context-receiver.ts +53 -0
- package/src/browser/panel-menu.tsx +56 -0
- package/src/browser/panel-messages.ts +35 -0
- package/src/browser/panel-shortcuts.ts +31 -0
- package/src/browser/panel-slot.ts +756 -0
- package/src/browser/panel.ts +65 -0
- package/src/browser/resource-picker.tsx +12 -1
- package/src/browser/search-commands.ts +6 -3
- package/src/capabilities/client.ts +8 -2
- package/src/capabilities/reviews.ts +205 -0
- package/src/capabilities/server.ts +10 -3
- package/src/capabilities/testing.ts +47 -0
- package/src/capabilities/types.ts +2 -2
- package/src/cli/account.ts +13 -3
- package/src/cli/admin/geoip.ts +115 -0
- package/src/cli/admin/index.ts +3 -0
- package/src/contracts/app-approval.ts +159 -12
- package/src/contracts/app.ts +14 -0
- package/src/contracts/approvals.ts +2 -2
- package/src/contracts/capabilities.ts +410 -18
- package/src/contracts/geoip.ts +42 -0
- package/src/contracts/index.ts +3 -0
- package/src/contracts/panel-context-plain.ts +99 -0
- package/src/contracts/panel-context.ts +93 -0
- package/src/contracts/registry.ts +3 -1
- package/src/contracts/seen-hints.ts +35 -0
- package/src/contracts/shared.ts +2 -0
- package/src/server/client-ip.ts +5 -0
- package/src/server/help-payload.ts +38 -0
- package/src/server/middleware/auth.ts +11 -1
- package/src/server/middleware/middleware.ts +9 -0
- package/src/server/middleware/no-transform.ts +19 -0
- package/src/server/middleware/rate-limit.ts +4 -6
- package/src/server/request-context/address.ts +45 -0
- package/src/server/request-context/geoip.ts +380 -0
- package/src/server/request-context/index.ts +33 -0
- package/src/server/request-context/user-agent.ts +95 -0
- package/src/server/services/access.ts +5 -1
- package/src/services/accounts/posix.ts +2 -1
- package/src/services/app-approval.ts +533 -90
- package/src/services/approvals/device.ts +9 -5
- package/src/services/approvals/index.ts +6 -5
- package/src/services/approvals/list.ts +26 -5
- package/src/services/auth-flows/notification-sender.ts +3 -0
- package/src/services/geoip/index.ts +164 -0
- package/src/services/geoip/store.ts +30 -0
- package/src/services/geoip/upload.ts +150 -0
- package/src/services/identity/routes.ts +5 -1
- package/src/services/index.ts +32 -3
- package/src/services/known-browsers.ts +312 -0
- package/src/services/login-address-store.ts +51 -0
- package/src/services/login-context.ts +118 -0
- package/src/services/mandates/index.ts +3 -0
- package/src/services/mandates/policy.ts +15 -3
- package/src/services/notifications/browser.ts +16 -5
- package/src/services/notifications/email-frame.ts +1 -1
- package/src/services/oauth-tokens.ts +16 -2
- package/src/services/panel-context-consent.ts +58 -0
- package/src/services/pdf/image-protocol.ts +135 -0
- package/src/services/pdf/image-variants.ts +124 -0
- package/src/services/pdf/image-worker.ts +31 -0
- package/src/services/pdf/image.ts +109 -0
- package/src/services/pdf/index.ts +9 -0
- package/src/services/service-account-credentials.ts +21 -0
- package/src/services/session/index.ts +97 -16
- package/src/services/session/recent.ts +13 -0
- package/src/services/settings/api.ts +11 -6
- package/src/services/settings/app.ts +5 -4
- package/src/services/settings/core-settings.ts +4 -3
- package/src/services/settings/index.ts +19 -17
- package/src/services/settings/namespace.ts +2 -1
- package/src/services/settings/store.ts +83 -11
- package/src/services/webauthn.ts +12 -0
- package/src/shared/ai-platform-prompt.ts +1 -1
- package/src/shared/app-presentation.ts +1 -0
- package/src/shared/email-html.ts +2 -0
- package/src/shared/help.ts +3 -0
- package/src/shared/redirect.ts +12 -0
- package/src/shared-text/cursor.ts +22 -0
- package/src/shared-text/documents.ts +142 -0
- package/src/shared-text/events.ts +43 -0
- package/src/shared-text/index.ts +12 -0
- package/src/shared-text/log.ts +48 -0
- package/src/shared-text/text-diff.ts +23 -0
- package/src/shared-text/types.ts +41 -0
- package/src/ssr/AdminLayout.tsx +2 -0
- package/src/ssr/AppLaunchpadPanel.tsx +2 -2
- package/src/ssr/CloudShell.island.tsx +95 -10
- package/src/ssr/Layout.tsx +66 -10
- package/src/ssr/LayoutHeader.tsx +15 -9
- package/src/ssr/LayoutHelp.browser-harness.tsx +2 -0
- package/src/ssr/LayoutHelp.tsx +435 -274
- package/src/ssr/LayoutHelpPanel.browser-harness.tsx +136 -0
- package/src/ssr/LayoutHelpTrigger.tsx +27 -2
- package/src/ssr/LayoutRail.tsx +2 -2
- package/src/ssr/LayoutTabBar.tsx +29 -0
- package/src/ssr/MobileNavigation.tsx +2 -2
- package/src/ssr/PanelLayout.tsx +35 -0
- package/src/ssr/PanelRuntime.island.tsx +94 -0
- package/src/ssr/PanelSlot.tsx +158 -0
- package/src/ssr/PanelTrigger.tsx +49 -0
- package/src/ssr/ShellRuntime.island.tsx +69 -5
- package/src/ssr/app-appearance.ts +3 -2
- package/src/ssr/app-color.ts +105 -0
- package/src/ssr/app-launchpad.tsx +18 -1
- package/src/ssr/app-navigation.ts +28 -0
- package/src/ssr/compact-shell.baseline.json +5270 -0
- package/src/ssr/compact-shell.browser-harness.ts +439 -0
- package/src/ssr/dialog-app-colors.browser-harness.tsx +43 -0
- package/src/ssr/help-messages.ts +4 -0
- package/src/ssr/help.ts +20 -0
- package/src/ssr/hints.ts +25 -0
- package/src/ssr/index.ts +5 -0
- package/src/ssr/islands/index.ts +4 -3
- package/src/ssr/layout-context.ts +2 -0
- package/src/ssr/layout-help-article.server.ts +50 -0
- package/src/ssr/layout-help-start.ts +33 -7
- package/src/ssr/panel-consent.ts +29 -0
- package/src/ssr/panel-context.ts +10 -0
- package/src/ssr/panel-slot.browser-harness.tsx +159 -0
- package/src/ssr/platform-messages.ts +48 -0
- package/src/ssr/rail-navigation.ts +44 -0
- package/src/ssr/shell-data.tsx +5 -0
- package/src/ssr/shell-shortcuts.ts +13 -2
- package/src/styles/effects.css +201 -11
- package/src/styles/file-chooser.css +2 -1
- package/src/styles/global.css +314 -63
- package/src/styles/panel.css +416 -0
- package/src/styles/resource-search.css +6 -2
- package/src/styles/tokens.css +36 -13
- package/src/styles/utilities-buttons.css +3 -2
- package/src/styles/utilities-data.css +2 -1
- package/src/styles/utilities-feedback.css +2 -2
- package/src/styles/utilities-layout.css +3 -13
- package/src/styles/utilities-markdown-editor.css +6 -6
- package/src/styles/utilities-navigation.css +22 -43
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { mergeSeenHints, parseSeenHintsCookieHeader, seenHintsSetCookie } from "../contracts/seen-hints";
|
|
2
|
+
|
|
3
|
+
/** Persist dismissal before navigating or closing the hint. */
|
|
4
|
+
export const markHintSeen = (id: string): void => {
|
|
5
|
+
document.cookie = seenHintsSetCookie(mergeSeenHints(parseSeenHintsCookieHeader(document.cookie), id), location.protocol === "https:");
|
|
6
|
+
};
|
|
@@ -10,6 +10,8 @@ export type LiveSubscriptionHandlers<T> = {
|
|
|
10
10
|
apply: (events: { data: T; cursor: string }[]) => Promise<void>;
|
|
11
11
|
/** Loads the canonical state again; events after it follow. */
|
|
12
12
|
resync: () => Promise<void>;
|
|
13
|
+
/** Reports changes in whether the subscription is confirmed and has no pending work. Never called after `close()`. */
|
|
14
|
+
caughtUp?: (caughtUp: boolean) => void;
|
|
13
15
|
/** The subscription ended because its resource is gone or no longer readable. */
|
|
14
16
|
revoked?: (code: "not_found" | "access_denied") => void;
|
|
15
17
|
/** Live updates stopped: the session ended, or `apply` or `resync` kept failing. Never called after `close()`. */
|
|
@@ -25,6 +27,7 @@ export type LiveConnection = {
|
|
|
25
27
|
type Subscriber = {
|
|
26
28
|
frame: () => unknown;
|
|
27
29
|
confirmed: boolean;
|
|
30
|
+
report: () => void;
|
|
28
31
|
receive: (message: LiveServerMessage) => void;
|
|
29
32
|
end: () => void;
|
|
30
33
|
};
|
|
@@ -56,9 +59,17 @@ const connect = (url: string, activity: "visible" | "always"): Shared => {
|
|
|
56
59
|
onOpen: (controls) => {
|
|
57
60
|
for (const subscriber of subscribers.values()) {
|
|
58
61
|
subscriber.confirmed = false;
|
|
62
|
+
subscriber.report();
|
|
59
63
|
controls.send(subscriber.frame());
|
|
60
64
|
}
|
|
61
65
|
},
|
|
66
|
+
onStatus: (status) => {
|
|
67
|
+
if (status === "open") return;
|
|
68
|
+
for (const subscriber of subscribers.values()) {
|
|
69
|
+
subscriber.confirmed = false;
|
|
70
|
+
subscriber.report();
|
|
71
|
+
}
|
|
72
|
+
},
|
|
62
73
|
onMessage: (message) => {
|
|
63
74
|
if (message.t === "progress") {
|
|
64
75
|
for (const subscriber of subscribers.values()) if (subscriber.confirmed) subscriber.receive(message);
|
|
@@ -96,8 +107,16 @@ export const liveConnection = (url: string, options: { activity?: "visible" | "a
|
|
|
96
107
|
let resyncAt: string | null = null;
|
|
97
108
|
let ended = false;
|
|
98
109
|
let running = false;
|
|
110
|
+
let caughtUp = false;
|
|
99
111
|
const work: Work<T>[] = [];
|
|
100
112
|
|
|
113
|
+
const report = () => {
|
|
114
|
+
const next = !ended && subscriber.confirmed && !running && work.length === 0;
|
|
115
|
+
if (next === caughtUp) return;
|
|
116
|
+
caughtUp = next;
|
|
117
|
+
handlers.caughtUp?.(next);
|
|
118
|
+
};
|
|
119
|
+
|
|
101
120
|
const attempt = async (run: () => Promise<void>): Promise<boolean> => {
|
|
102
121
|
for (let tries = 0; ; tries++) {
|
|
103
122
|
try {
|
|
@@ -122,6 +141,7 @@ export const liveConnection = (url: string, options: { activity?: "visible" | "a
|
|
|
122
141
|
cursor = item.cursor;
|
|
123
142
|
continue;
|
|
124
143
|
}
|
|
144
|
+
report();
|
|
125
145
|
const applied = item.kind === "events" ? await attempt(() => handlers.apply(item.events)) : await attempt(handlers.resync);
|
|
126
146
|
if (!applied) {
|
|
127
147
|
// A closed subscription reports nothing: its owner ended it.
|
|
@@ -135,6 +155,7 @@ export const liveConnection = (url: string, options: { activity?: "visible" | "a
|
|
|
135
155
|
}
|
|
136
156
|
} finally {
|
|
137
157
|
running = false;
|
|
158
|
+
report();
|
|
138
159
|
}
|
|
139
160
|
};
|
|
140
161
|
|
|
@@ -153,6 +174,7 @@ export const liveConnection = (url: string, options: { activity?: "visible" | "a
|
|
|
153
174
|
return { t: "sub", id, channel, scope, ...(after ? { after } : {}) };
|
|
154
175
|
},
|
|
155
176
|
confirmed: false,
|
|
177
|
+
report,
|
|
156
178
|
receive: (message) => {
|
|
157
179
|
if (ended) return;
|
|
158
180
|
if (message.t === "revoked") {
|
|
@@ -189,10 +211,12 @@ export const liveConnection = (url: string, options: { activity?: "visible" | "a
|
|
|
189
211
|
},
|
|
190
212
|
};
|
|
191
213
|
|
|
192
|
-
const stop = () => {
|
|
214
|
+
const stop = (notify = true) => {
|
|
193
215
|
if (ended) return;
|
|
194
216
|
ended = true;
|
|
195
217
|
work.length = 0;
|
|
218
|
+
if (notify) report();
|
|
219
|
+
else caughtUp = false;
|
|
196
220
|
if (subscribers.get(id) !== subscriber) return;
|
|
197
221
|
subscribers.delete(id);
|
|
198
222
|
socket.send({ t: "unsub", id });
|
|
@@ -205,6 +229,6 @@ export const liveConnection = (url: string, options: { activity?: "visible" | "a
|
|
|
205
229
|
subscribers.set(id, subscriber);
|
|
206
230
|
if (subscribers.size === 1) socket.connect();
|
|
207
231
|
else socket.send(subscriber.frame());
|
|
208
|
-
return { close: stop };
|
|
232
|
+
return { close: () => stop(false) };
|
|
209
233
|
},
|
|
210
234
|
});
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { readShellData } from "../ssr/shell-data";
|
|
2
|
+
|
|
3
|
+
/** Navigation controls discoverability, never authorization; the target application still checks access. */
|
|
4
|
+
export const navigationApps = (): { appIds: ReadonlySet<string>; currentAppId?: string } => {
|
|
5
|
+
const shell = readShellData();
|
|
6
|
+
return { appIds: new Set(shell?.apps.map((app) => app.id) ?? []), currentAppId: shell?.appId };
|
|
7
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { CloudResourceRef } from "../contracts/capabilities";
|
|
2
|
+
import type { PanelPageContext } from "../contracts/panel-context";
|
|
3
|
+
|
|
4
|
+
export const PAGE_CONTEXT_COLLECT = "cloud.page-context.collect";
|
|
5
|
+
export const PAGE_CONTEXT_CHANGED = "cloud.page-context.changed";
|
|
6
|
+
export const PANEL_CONTEXT_CHANGED = "cloud.panel.context.changed";
|
|
7
|
+
export const PANEL_CONTEXT_ELEMENT = "cloud-panel-context";
|
|
8
|
+
export const PANEL_SHORTCUTS_ELEMENT = "cloud-panel-shortcuts";
|
|
9
|
+
export const PANEL_SHORTCUTS_CHANGED = "cloud.panel.shortcuts.changed";
|
|
10
|
+
export type PageContextSource = { scope?: CloudResourceRef };
|
|
11
|
+
export type PageContextCollection = { sources: PageContextSource[] };
|
|
12
|
+
export type PanelConsentRequest = { provider: string; allowed: boolean };
|
|
13
|
+
export type PanelState = { provider: string; appName: string; consent: boolean; allowed: boolean; saving: boolean; open: boolean };
|
|
14
|
+
|
|
15
|
+
declare global {
|
|
16
|
+
interface WindowEventMap {
|
|
17
|
+
"cloud.panel.consent": CustomEvent<PanelConsentRequest>;
|
|
18
|
+
"cloud.panel.state": CustomEvent<PanelState>;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Framework-validated SSR data, then receiver-validated messages. Shared DOM state survives independent bundles. */
|
|
23
|
+
export function readProviderContext(): PanelPageContext | null {
|
|
24
|
+
if (typeof document === "undefined") return null;
|
|
25
|
+
try {
|
|
26
|
+
return JSON.parse(document.getElementById(PANEL_CONTEXT_ELEMENT)?.textContent ?? "null");
|
|
27
|
+
} catch {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { isPanelContextMessage } from "../contracts/panel-context-plain";
|
|
2
|
+
import {
|
|
3
|
+
PANEL_CONTEXT_CHANGED,
|
|
4
|
+
PANEL_CONTEXT_ELEMENT,
|
|
5
|
+
PANEL_SHORTCUTS_CHANGED,
|
|
6
|
+
PANEL_SHORTCUTS_ELEMENT,
|
|
7
|
+
readProviderContext,
|
|
8
|
+
} from "./panel-context-bridge";
|
|
9
|
+
|
|
10
|
+
/** Installed only by an embedded PanelRuntime; the public page publisher never imports validation. */
|
|
11
|
+
export function installPanelContextReceiver(): () => void {
|
|
12
|
+
let element = document.getElementById(PANEL_CONTEXT_ELEMENT);
|
|
13
|
+
if (!element) {
|
|
14
|
+
element = document.createElement("script");
|
|
15
|
+
element.id = PANEL_CONTEXT_ELEMENT;
|
|
16
|
+
element.setAttribute("type", "application/json");
|
|
17
|
+
element.textContent = "null";
|
|
18
|
+
document.body.append(element);
|
|
19
|
+
}
|
|
20
|
+
let shortcutsElement = document.getElementById(PANEL_SHORTCUTS_ELEMENT);
|
|
21
|
+
if (!shortcutsElement) {
|
|
22
|
+
shortcutsElement = document.createElement("script");
|
|
23
|
+
shortcutsElement.id = PANEL_SHORTCUTS_ELEMENT;
|
|
24
|
+
shortcutsElement.setAttribute("type", "application/json");
|
|
25
|
+
shortcutsElement.textContent = "[]";
|
|
26
|
+
document.body.append(shortcutsElement);
|
|
27
|
+
}
|
|
28
|
+
const first = readProviderContext();
|
|
29
|
+
let pageId = first?.pageId;
|
|
30
|
+
let seq = first?.seq ?? 0;
|
|
31
|
+
const receive = (event: MessageEvent<unknown>) => {
|
|
32
|
+
if (event.source !== window.parent || event.origin !== location.origin || !isPanelContextMessage(event.data)) return;
|
|
33
|
+
const context = event.data.context;
|
|
34
|
+
if (context) {
|
|
35
|
+
if ((pageId !== undefined && context.pageId !== pageId) || context.seq < seq) return;
|
|
36
|
+
pageId = context.pageId;
|
|
37
|
+
seq = context.seq;
|
|
38
|
+
}
|
|
39
|
+
// Null revokes the current context without forgetting document identity or ordering.
|
|
40
|
+
const contextJson = JSON.stringify(context);
|
|
41
|
+
const contextChanged = element.textContent !== contextJson;
|
|
42
|
+
element.textContent = contextJson;
|
|
43
|
+
const shortcuts = JSON.stringify(context ? (event.data.shortcuts ?? []) : []);
|
|
44
|
+
const shortcutsChanged = shortcutsElement.textContent !== shortcuts;
|
|
45
|
+
shortcutsElement.textContent = shortcuts;
|
|
46
|
+
// Remember accepted snapshots, including empty ones, across Help remounts.
|
|
47
|
+
shortcutsElement.setAttribute("data-received", "");
|
|
48
|
+
if (shortcutsChanged) window.dispatchEvent(new Event(PANEL_SHORTCUTS_CHANGED));
|
|
49
|
+
if (contextChanged || !shortcutsChanged) window.dispatchEvent(new Event(PANEL_CONTEXT_CHANGED));
|
|
50
|
+
};
|
|
51
|
+
window.addEventListener("message", receive);
|
|
52
|
+
return () => window.removeEventListener("message", receive);
|
|
53
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { Dropdown } from "@k2b/ui";
|
|
2
|
+
import { createSignal, onCleanup, onMount } from "solid-js";
|
|
3
|
+
import { platformMessages } from "../ssr/platform-messages";
|
|
4
|
+
import type { PanelState } from "./panel-context-bridge";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The slot head's ⋯ menu for providers that need consent. The lazy slot controller mounts it over the server's
|
|
8
|
+
* placeholder button, so it costs no island and can never change an answer before the controller listens. Only
|
|
9
|
+
* document events cross the menu/controller boundary.
|
|
10
|
+
*/
|
|
11
|
+
export default function PanelMenu(props: { initial: PanelState; locale: string }) {
|
|
12
|
+
const t = () => platformMessages.resolve([props.locale]).t;
|
|
13
|
+
const [state, setState] = createSignal(props.initial);
|
|
14
|
+
const [open, setOpen] = createSignal(false);
|
|
15
|
+
onMount(() => {
|
|
16
|
+
const changed = (event: CustomEvent<PanelState>) => {
|
|
17
|
+
// A closed slot, another provider or one without consent leaves no reason for the menu to stay expanded.
|
|
18
|
+
if (!event.detail.open || event.detail.provider !== state().provider || !event.detail.consent) setOpen(false);
|
|
19
|
+
setState(event.detail);
|
|
20
|
+
};
|
|
21
|
+
window.addEventListener("cloud.panel.state", changed);
|
|
22
|
+
onCleanup(() => window.removeEventListener("cloud.panel.state", changed));
|
|
23
|
+
});
|
|
24
|
+
return (
|
|
25
|
+
<Dropdown.Root
|
|
26
|
+
open={open()}
|
|
27
|
+
onOpenChange={setOpen}
|
|
28
|
+
label={t().panelMenu}
|
|
29
|
+
position="bottom-left"
|
|
30
|
+
items={[
|
|
31
|
+
{
|
|
32
|
+
sectionLabel: t().panelFrom({ app: state().appName }),
|
|
33
|
+
items: [
|
|
34
|
+
{
|
|
35
|
+
label: t().panelShareContext,
|
|
36
|
+
choice: "checkbox",
|
|
37
|
+
// A getter: the menu keeps its rows, so a plain value would show the answer from when it opened.
|
|
38
|
+
checked: () => state().allowed,
|
|
39
|
+
disabled: state().saving,
|
|
40
|
+
action: () =>
|
|
41
|
+
window.dispatchEvent(
|
|
42
|
+
new CustomEvent("cloud.panel.consent", {
|
|
43
|
+
detail: { provider: state().provider, allowed: !state().allowed },
|
|
44
|
+
}),
|
|
45
|
+
),
|
|
46
|
+
},
|
|
47
|
+
],
|
|
48
|
+
},
|
|
49
|
+
]}
|
|
50
|
+
>
|
|
51
|
+
<Dropdown.Trigger iconOnly label={t().panelMenu} disabled={!state().consent}>
|
|
52
|
+
<i class="ti ti-dots" aria-hidden="true" />
|
|
53
|
+
</Dropdown.Trigger>
|
|
54
|
+
</Dropdown.Root>
|
|
55
|
+
);
|
|
56
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
const isRecord = (value: unknown): value is Record<string, unknown> => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2
|
+
|
|
3
|
+
export function isPanelReadyMessage(value: unknown, provider: string): value is { type: "cloud.panel.ready"; v: 1; provider: string } {
|
|
4
|
+
return (
|
|
5
|
+
isRecord(value) &&
|
|
6
|
+
Reflect.ownKeys(value).length === 3 &&
|
|
7
|
+
Object.hasOwn(value, "type") &&
|
|
8
|
+
Object.hasOwn(value, "v") &&
|
|
9
|
+
Object.hasOwn(value, "provider") &&
|
|
10
|
+
value.type === "cloud.panel.ready" &&
|
|
11
|
+
value.v === 1 &&
|
|
12
|
+
typeof value.provider === "string" &&
|
|
13
|
+
value.provider === provider
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function isPanelSearchMessage(value: unknown): value is { type: "cloud.panel.search"; v: 1 } {
|
|
18
|
+
return (
|
|
19
|
+
isRecord(value) &&
|
|
20
|
+
Reflect.ownKeys(value).length === 2 &&
|
|
21
|
+
Object.hasOwn(value, "type") &&
|
|
22
|
+
Object.hasOwn(value, "v") &&
|
|
23
|
+
value.type === "cloud.panel.search" &&
|
|
24
|
+
value.v === 1
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function isPanelCloseMessage(value: unknown): value is { type: "cloud.panel.close"; v?: 1 } {
|
|
29
|
+
return (
|
|
30
|
+
isRecord(value) &&
|
|
31
|
+
Object.hasOwn(value, "type") &&
|
|
32
|
+
value.type === "cloud.panel.close" &&
|
|
33
|
+
(Reflect.ownKeys(value).length === 1 || (Reflect.ownKeys(value).length === 2 && Object.hasOwn(value, "v") && value.v === 1))
|
|
34
|
+
);
|
|
35
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { isPanelContextMessage } from "../contracts/panel-context-plain";
|
|
2
|
+
import type { HelpShortcut } from "../ssr/layout-help-start";
|
|
3
|
+
import { PANEL_SHORTCUTS_CHANGED, PANEL_SHORTCUTS_ELEMENT, readProviderContext } from "./panel-context-bridge";
|
|
4
|
+
|
|
5
|
+
/** Only platform Help code imports this module; third-party providers never receive a list. */
|
|
6
|
+
export function readPanelShortcuts(): HelpShortcut[] {
|
|
7
|
+
if (typeof document === "undefined") return [];
|
|
8
|
+
try {
|
|
9
|
+
const shortcuts: unknown = JSON.parse(document.getElementById(PANEL_SHORTCUTS_ELEMENT)?.textContent ?? "[]");
|
|
10
|
+
const message = {
|
|
11
|
+
type: "cloud.panel.context",
|
|
12
|
+
v: 1,
|
|
13
|
+
context: readProviderContext(),
|
|
14
|
+
shortcuts,
|
|
15
|
+
};
|
|
16
|
+
return isPanelContextMessage(message) ? (message.shortcuts ?? []) : [];
|
|
17
|
+
} catch {
|
|
18
|
+
return [];
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function panelShortcutsReceived(): boolean {
|
|
23
|
+
if (typeof document === "undefined") return false;
|
|
24
|
+
return document.getElementById(PANEL_SHORTCUTS_ELEMENT)?.hasAttribute("data-received") ?? false;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function onPanelShortcutsChange(cb: (shortcuts: HelpShortcut[]) => void): () => void {
|
|
28
|
+
const changed = () => cb(readPanelShortcuts());
|
|
29
|
+
window.addEventListener(PANEL_SHORTCUTS_CHANGED, changed);
|
|
30
|
+
return () => window.removeEventListener(PANEL_SHORTCUTS_CHANGED, changed);
|
|
31
|
+
}
|