@k2b/cloud 0.39.0 → 0.41.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 +11 -4
- package/src/_internal/app-favicon.ts +4 -2
- 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 +217 -1
- package/src/_internal/capability-streams.ts +7 -0
- package/src/_internal/define-app.ts +49 -18
- package/src/_internal/font-preloads.ts +2 -1
- package/src/_internal/heartbeat.ts +203 -14
- package/src/_internal/registry.ts +28 -1
- package/src/_internal/runtime-watcher.ts +6 -0
- package/src/_internal/setup-lock.ts +103 -0
- package/src/_internal/static-assets.ts +52 -3
- package/src/_internal/status-preserving-ssr.ts +2 -0
- package/src/access/PermissionEditor.tsx +2 -2
- package/src/account/EntitySearch.tsx +1 -1
- package/src/ai/action-review.ts +101 -0
- package/src/ai/background-review.ts +72 -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 +464 -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 +680 -0
- package/src/ai/chat/action-text.ts +288 -0
- package/src/ai/chat/blocks.tsx +19 -25
- package/src/ai/chat/message-actions.tsx +6 -0
- package/src/ai/chat/tool-action-words.ts +31 -0
- package/src/ai/chat/tool-action.tsx +456 -0
- package/src/ai/chat/turn-view.tsx +10 -160
- package/src/ai/chat/view-block.tsx +15 -5
- package/src/ai/chat-task-contracts.ts +4 -1
- package/src/ai/chat-task-reasons-migrate.ts +7 -0
- package/src/ai/chat-task-reasons.ts +1 -1
- package/src/ai/chat-tasks.ts +55 -25
- package/src/ai/client/controller.ts +59 -0
- package/src/ai/default-tools.ts +9 -1
- package/src/ai/executor.ts +198 -39
- package/src/ai/firecrawl-tools.ts +15 -2
- package/src/ai/index.ts +3 -0
- package/src/ai/migrate.ts +26 -2
- package/src/ai/open-questions.ts +172 -0
- package/src/ai/projects-routes.ts +3 -0
- package/src/ai/protocol.ts +14 -2
- package/src/ai/question-text.ts +8 -0
- package/src/ai/recent-resources.ts +240 -0
- package/src/ai/recent-sent-refs.ts +38 -0
- package/src/ai/routes.ts +195 -5
- package/src/ai/runtime.ts +1 -0
- package/src/ai/skills-routes.ts +3 -0
- package/src/ai/skills.ts +10 -1
- package/src/ai/store.ts +245 -24
- package/src/ai/testing.ts +36 -0
- package/src/ai/tool-audit.ts +8 -0
- package/src/ai/tool-sentences.ts +152 -34
- package/src/ai/tool-wording.ts +36 -8
- package/src/ai/tools.ts +8 -3
- package/src/ai/types.ts +43 -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-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 +2 -0
- package/src/api/app-approval-actions.ts +31 -7
- package/src/api/app-approval.ts +46 -5
- package/src/api/approvals-loader.ts +23 -1
- package/src/api/approvals.ts +2 -0
- package/src/api/auth/schemas.ts +3 -0
- package/src/api/auth.ts +67 -12
- 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 +6 -1
- package/src/api/lasting-access-messages.ts +17 -0
- package/src/api/mcp.ts +30 -3
- package/src/api/me-panel-consent.ts +71 -0
- package/src/api/me.ts +159 -36
- 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 +166 -262
- package/src/approvals/approval-card.ts +68 -0
- package/src/approvals/messages.ts +12 -24
- package/src/browser/CloudResourceSearch.browser-harness.tsx +10 -2
- package/src/browser/app-approval.ts +54 -5
- package/src/browser/commands.ts +22 -6
- package/src/browser/composer-catalog.ts +45 -3
- package/src/browser/composer-insert.ts +10 -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 +11 -0
- package/src/browser/panel-shortcuts.ts +31 -0
- package/src/browser/panel-slot.ts +412 -10
- package/src/browser/panel.ts +37 -0
- 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 +123 -16
- 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/seen-hints.ts +35 -0
- package/src/contracts/shared.ts +3 -0
- package/src/server/client-ip.ts +5 -0
- package/src/server/help-payload.ts +38 -0
- package/src/server/middleware/auth.ts +13 -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/services/accounts/app.ts +23 -3
- package/src/services/accounts/users.ts +12 -1
- package/src/services/app-approval.ts +382 -59
- package/src/services/approvals/device.ts +49 -31
- package/src/services/approvals/index.ts +7 -5
- package/src/services/approvals/list.ts +27 -6
- package/src/services/approvals/question.ts +68 -0
- package/src/services/audit/index.ts +1 -0
- package/src/services/auth-flows/ipa.ts +39 -2
- package/src/services/auth-flows/notification-sender.ts +3 -0
- package/src/services/auth-flows/password-reset.ts +32 -6
- 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 +13 -0
- 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/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/markdown.ts +160 -2
- package/src/services/service-account-credentials.ts +21 -0
- package/src/services/session/index.ts +296 -41
- package/src/services/session/recent.ts +13 -0
- package/src/services/webauthn.ts +12 -0
- package/src/shared/email-html.ts +2 -0
- package/src/shared/help.ts +3 -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 +10 -2
- package/src/ssr/HelpPanelArticle.island.tsx +308 -0
- package/src/ssr/HelpPanelBack.island.tsx +32 -0
- package/src/ssr/HelpPanelMenu.island.tsx +133 -0
- package/src/ssr/HelpPanelSearch.island.tsx +123 -0
- package/src/ssr/HelpPanelShortcuts.island.tsx +22 -0
- package/src/ssr/HelpPanelView.island.tsx +27 -0
- package/src/ssr/Layout.tsx +25 -2
- package/src/ssr/LayoutHelp.browser-harness.tsx +2 -0
- package/src/ssr/LayoutHelp.tsx +222 -419
- package/src/ssr/LayoutHelpPanel.browser-harness.tsx +135 -0
- package/src/ssr/LayoutHelpPanel.server.browser-harness.tsx +131 -0
- package/src/ssr/LayoutHelpPanel.server.tsx +115 -0
- package/src/ssr/LayoutHelpTrigger.tsx +27 -2
- package/src/ssr/MobileNavigation.tsx +2 -2
- package/src/ssr/PanelLayout.tsx +9 -1
- package/src/ssr/PanelRuntime.island.tsx +7 -0
- package/src/ssr/PanelSlot.tsx +53 -3
- package/src/ssr/ShellRuntime.island.tsx +11 -2
- package/src/ssr/__snapshots__/LayoutHelp.ssr.render.test.tsx.snap +29 -0
- package/src/ssr/app-color.ts +14 -9
- package/src/ssr/app-navigation.ts +16 -3
- package/src/ssr/compact-shell.browser-harness.ts +30 -22
- package/src/ssr/dialog-app-colors.browser-harness.tsx +43 -0
- package/src/ssr/help-messages.ts +4 -0
- package/src/ssr/help.ts +21 -0
- package/src/ssr/hints.ts +25 -0
- package/src/ssr/index.ts +3 -0
- package/src/ssr/layout-context.ts +2 -0
- package/src/ssr/layout-help-article.server.ts +50 -0
- package/src/ssr/layout-help-panel-controls.tsx +43 -0
- package/src/ssr/layout-help-panel-document.ts +67 -0
- package/src/ssr/layout-help-panel-lifecycle.ts +32 -0
- package/src/ssr/layout-help-panel-menu.tsx +113 -0
- package/src/ssr/layout-help-panel-modules.ts +7 -0
- package/src/ssr/layout-help-panel-reader.tsx +159 -0
- package/src/ssr/layout-help-panel-search.tsx +69 -0
- package/src/ssr/layout-help-panel-shortcuts.ts +7 -0
- package/src/ssr/layout-help-panel-state.ts +141 -0
- package/src/ssr/layout-help-panel-types.ts +19 -0
- package/src/ssr/layout-help-start.ts +33 -7
- package/src/ssr/layout-help-views.tsx +484 -0
- package/src/ssr/panel-consent.ts +29 -0
- package/src/ssr/panel-context.ts +10 -0
- package/src/ssr/panel-idle.ts +9 -0
- package/src/ssr/panel-slot.browser-harness.tsx +53 -4
- package/src/ssr/platform-messages.ts +18 -0
- package/src/ssr/shell-data.tsx +2 -1
- package/src/styles/approvals.css +6 -0
- package/src/styles/effects.css +213 -9
- package/src/styles/file-chooser.css +2 -1
- package/src/styles/global.css +49 -12
- package/src/styles/panel.css +47 -4
- package/src/styles/resource-search.css +6 -2
- package/src/styles/tokens.css +17 -11
- 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 +17 -22
package/src/ssr/LayoutHelp.tsx
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { HelpArticleLink, HelpDocumentManifest, HelpDocumentPayload } from "@k2b/cloud/shared";
|
|
2
2
|
import { clipboard } from "@k2b/stdlib/solid";
|
|
3
3
|
import {
|
|
4
|
+
BottomSheet,
|
|
4
5
|
Button,
|
|
6
|
+
bottomSheetOptions,
|
|
5
7
|
Dropdown,
|
|
8
|
+
dialogCore,
|
|
6
9
|
IconButton,
|
|
7
|
-
Kbd,
|
|
8
10
|
MarkdownView,
|
|
9
11
|
NoticeCard,
|
|
10
12
|
Placeholder,
|
|
@@ -12,28 +14,24 @@ import {
|
|
|
12
14
|
prompts,
|
|
13
15
|
ScrollArea,
|
|
14
16
|
TableOfContents,
|
|
15
|
-
TextInput,
|
|
16
17
|
toast,
|
|
17
18
|
useLocale,
|
|
18
19
|
} from "@k2b/ui";
|
|
19
|
-
import { createEffect, createMemo, createSignal,
|
|
20
|
+
import { createEffect, createMemo, createSignal, type JSX, onCleanup, onMount, Show, untrack } from "solid-js";
|
|
20
21
|
import { COMMANDS_CHANGED, contextCommandsWithShortcuts } from "../browser/command-shortcuts";
|
|
22
|
+
import { openPanel } from "../browser/panel";
|
|
21
23
|
import { HELP_ACCESS_LEVELS, HELP_KINDS } from "../shared/help";
|
|
22
24
|
import { appAccentStyle } from "./app-appearance";
|
|
25
|
+
import type { LayoutHelpArticle } from "./help";
|
|
23
26
|
import { helpMessages } from "./help-messages";
|
|
24
27
|
import { helpArticleParts } from "./layout-help-article";
|
|
25
28
|
import { formatHelpBundleMarkdown, formatHelpDocumentMarkdown } from "./layout-help-markdown";
|
|
26
29
|
import { focusHelpArticleHeading, resetHelpArticleScroll } from "./layout-help-navigation";
|
|
27
30
|
import { HELP_TOPICS_EVENT, type HelpTopic, registry } from "./layout-help-registry";
|
|
28
31
|
import { searchHelpTopics } from "./layout-help-search";
|
|
29
|
-
import {
|
|
30
|
-
type HelpShortcutCommand,
|
|
31
|
-
type HelpShortcutScope,
|
|
32
|
-
type HelpStartGroupKind,
|
|
33
|
-
helpShortcutGroups,
|
|
34
|
-
helpStart,
|
|
35
|
-
} from "./layout-help-start";
|
|
32
|
+
import { type HelpShortcutCommand, helpShortcutGroups, helpStart } from "./layout-help-start";
|
|
36
33
|
import { helpLinkTarget, layoutHelpTopicHref } from "./layout-help-url";
|
|
34
|
+
import { HelpArticleView, HelpHead, HelpResultsView, HelpShortcutsView, HelpStartView, type HelpSurface } from "./layout-help-views";
|
|
37
35
|
import { platformMessages } from "./platform-messages";
|
|
38
36
|
import { readShellData, useShellData } from "./shell-data";
|
|
39
37
|
import { shellShortcuts } from "./shell-shortcuts";
|
|
@@ -41,6 +39,8 @@ import { shellShortcuts } from "./shell-shortcuts";
|
|
|
41
39
|
export type LayoutHelpPageProps = {
|
|
42
40
|
documents: readonly HelpDocumentManifest[];
|
|
43
41
|
initialTopic?: string;
|
|
42
|
+
/** Authorized article and server-parsed parts for an identical first frame. */
|
|
43
|
+
initialArticle?: LayoutHelpArticle;
|
|
44
44
|
includeShortcuts?: boolean;
|
|
45
45
|
accent?: string;
|
|
46
46
|
/** Render inside an app-owned pane instead of occupying a full page. */
|
|
@@ -76,8 +76,6 @@ const articleLink = (value: unknown): HelpArticleLink | undefined => {
|
|
|
76
76
|
return { appId, appName, accent, documentId, title, description, icon, url };
|
|
77
77
|
};
|
|
78
78
|
|
|
79
|
-
/** An article without its own icon gets a neutral page tile. */
|
|
80
|
-
const iconClass = (icon?: string) => (icon?.startsWith("ti ") ? icon : `ti ${icon ?? "ti-file-text"}`);
|
|
81
79
|
const byOrder = (a: HelpTopic, b: HelpTopic) => (a.order ?? 100) - (b.order ?? 100) || a.title.localeCompare(b.title);
|
|
82
80
|
const sortedTopics = () => [...(registry()?.values() ?? [])].sort(byOrder);
|
|
83
81
|
const mergeTopics = (registered: readonly HelpTopic[], documents: readonly HelpDocumentManifest[] = []): HelpTopic[] => {
|
|
@@ -85,11 +83,6 @@ const mergeTopics = (registered: readonly HelpTopic[], documents: readonly HelpD
|
|
|
85
83
|
for (const topic of documents) topics.set(topic.id, topic);
|
|
86
84
|
return [...topics.values()].sort(byOrder);
|
|
87
85
|
};
|
|
88
|
-
const GROUP_ICONS: Record<HelpStartGroupKind, string> = {
|
|
89
|
-
start: "ti ti-rocket",
|
|
90
|
-
reference: "ti ti-book-2",
|
|
91
|
-
troubleshoot: "ti ti-lifebuoy",
|
|
92
|
-
};
|
|
93
86
|
|
|
94
87
|
/**
|
|
95
88
|
* Keyboard shortcuts come last in the start. The browser knows the page's commands. On a Layout page (it has shell
|
|
@@ -99,7 +92,6 @@ const GROUP_ICONS: Record<HelpStartGroupKind, string> = {
|
|
|
99
92
|
*/
|
|
100
93
|
const StartShortcuts = (props: { appName?: string }) => {
|
|
101
94
|
const locale = useLocale();
|
|
102
|
-
const t = () => helpMessages.resolve([locale()]).t;
|
|
103
95
|
const shell = useShellData();
|
|
104
96
|
const seed: HelpShortcutCommand[] = shell ? shellShortcuts(platformMessages.resolve([locale()]).t, { panel: !!shell.panel }) : [];
|
|
105
97
|
const read = (): HelpShortcutCommand[] => {
|
|
@@ -113,53 +105,20 @@ const StartShortcuts = (props: { appName?: string }) => {
|
|
|
113
105
|
window.addEventListener(COMMANDS_CHANGED, update);
|
|
114
106
|
onCleanup(() => window.removeEventListener(COMMANDS_CHANGED, update));
|
|
115
107
|
});
|
|
116
|
-
|
|
117
|
-
scope === "everywhere"
|
|
118
|
-
? t().shortcutsEverywhere
|
|
119
|
-
: scope === "selection"
|
|
120
|
-
? t().shortcutsWithSelection
|
|
121
|
-
: props.appName
|
|
122
|
-
? t().shortcutsInApp({ app: props.appName })
|
|
123
|
-
: t().shortcutsInThisApp;
|
|
124
|
-
return (
|
|
125
|
-
<section class="help-start-section help-start-shortcuts" data-help-shortcuts="" aria-labelledby="help-start-shortcuts-title">
|
|
126
|
-
<h3 id="help-start-shortcuts-title" class="help-start-heading">
|
|
127
|
-
<i class="ti ti-keyboard" aria-hidden="true" />
|
|
128
|
-
{t().keyboardShortcuts}
|
|
129
|
-
</h3>
|
|
130
|
-
<div class="help-shortcut-groups">
|
|
131
|
-
<For each={helpShortcutGroups(commands())}>
|
|
132
|
-
{(group) => (
|
|
133
|
-
<section class="help-shortcut-group" data-help-shortcut-group={group.scope} aria-label={label(group.scope)}>
|
|
134
|
-
<h4 class="help-shortcut-group-title" aria-hidden="true">
|
|
135
|
-
{label(group.scope)}
|
|
136
|
-
</h4>
|
|
137
|
-
<ul>
|
|
138
|
-
<For each={group.shortcuts}>
|
|
139
|
-
{(command) => (
|
|
140
|
-
<li class="help-shortcut" title={command.description}>
|
|
141
|
-
<span class="help-shortcut-title">{command.title}</span>
|
|
142
|
-
<Kbd keys={command.shortcut} platform={shell?.platform} />
|
|
143
|
-
</li>
|
|
144
|
-
)}
|
|
145
|
-
</For>
|
|
146
|
-
</ul>
|
|
147
|
-
</section>
|
|
148
|
-
)}
|
|
149
|
-
</For>
|
|
150
|
-
</div>
|
|
151
|
-
</section>
|
|
152
|
-
);
|
|
108
|
+
return <HelpShortcutsView groups={helpShortcutGroups(commands())} appName={props.appName} platform={shell?.platform} />;
|
|
153
109
|
};
|
|
154
110
|
|
|
155
111
|
const HelpShell = (props: {
|
|
156
112
|
session: HelpSession;
|
|
113
|
+
initialArticle?: LayoutHelpArticle;
|
|
157
114
|
close: () => void;
|
|
158
115
|
pageBase?: string;
|
|
159
116
|
documents?: readonly HelpDocumentManifest[];
|
|
160
117
|
includeShortcuts?: boolean;
|
|
161
118
|
accent?: string;
|
|
162
|
-
|
|
119
|
+
/** The app's canvas colours for a reader outside the app canvas. */
|
|
120
|
+
appearance?: string;
|
|
121
|
+
surface: HelpSurface;
|
|
163
122
|
syncPageUrl?: boolean;
|
|
164
123
|
}) => {
|
|
165
124
|
const locale = useLocale();
|
|
@@ -169,13 +128,20 @@ const HelpShell = (props: {
|
|
|
169
128
|
const appId = shell?.appId;
|
|
170
129
|
const app = shell?.apps.find((candidate) => candidate.id === appId);
|
|
171
130
|
const appName = app?.label;
|
|
172
|
-
const
|
|
173
|
-
const [externalTopics, setExternalTopics] = createSignal(mergeTopics(
|
|
131
|
+
const registeredTopics = () => sortedTopics();
|
|
132
|
+
const [externalTopics, setExternalTopics] = createSignal(mergeTopics(registeredTopics(), props.documents));
|
|
174
133
|
const [view, setView] = createSignal<HelpView>(props.session.view);
|
|
175
134
|
const [query, setQuery] = createSignal(props.session.query);
|
|
176
135
|
const [activeId, setActiveId] = createSignal<string | null>(props.session.activeId);
|
|
177
|
-
const
|
|
178
|
-
const
|
|
136
|
+
const initialTopic = props.documents?.find((topic) => topic.id === props.session.activeId);
|
|
137
|
+
const initialArticle = initialTopic && props.initialArticle?.payload.id === initialTopic.id ? props.initialArticle : undefined;
|
|
138
|
+
// HTML travels once as parts; rebuild it for the cache and the article settle effect.
|
|
139
|
+
const seeded = initialArticle
|
|
140
|
+
? { ...initialArticle.payload, html: (initialArticle.parts.lead ?? "") + initialArticle.parts.body }
|
|
141
|
+
: undefined;
|
|
142
|
+
if (seeded && initialTopic) props.session.articleCache.set(`${initialTopic.locale ?? "en"}:${initialTopic.url}`, seeded);
|
|
143
|
+
const [payload, setPayload] = createSignal<HelpDocumentPayload | null>(seeded ?? null);
|
|
144
|
+
const [loading, setLoading] = createSignal(props.session.view === "article" && !initialArticle);
|
|
179
145
|
const [loadError, setLoadError] = createSignal<string | null>(null);
|
|
180
146
|
const [loadAttempt, setLoadAttempt] = createSignal(0);
|
|
181
147
|
const [remoteIds, setRemoteIds] = createSignal<readonly string[] | null>(null);
|
|
@@ -269,10 +235,11 @@ const HelpShell = (props: {
|
|
|
269
235
|
const topics = externalTopics;
|
|
270
236
|
const start = createMemo(() => helpStart(topics(), props.surface === "modal" ? shell?.help : undefined));
|
|
271
237
|
const activeTopic = createMemo(() => topics().find((topic) => topic.id === activeId()) ?? null);
|
|
272
|
-
//
|
|
238
|
+
// Reuse the serialized parts for the initial object on both sides of the island boundary.
|
|
273
239
|
const articleParts = createMemo(() => {
|
|
274
|
-
const
|
|
275
|
-
|
|
240
|
+
const article = payload();
|
|
241
|
+
if (initialArticle && article === seeded) return initialArticle.parts;
|
|
242
|
+
return article?.html ? helpArticleParts(article.html) : null;
|
|
276
243
|
});
|
|
277
244
|
const articleSections = (): HelpSection[] => (view() === "article" ? articleParts()?.sections : undefined) ?? [];
|
|
278
245
|
const normalizedQuery = createMemo(() => query().trim().toLocaleLowerCase());
|
|
@@ -362,9 +329,31 @@ const HelpShell = (props: {
|
|
|
362
329
|
createEffect(syncSession);
|
|
363
330
|
|
|
364
331
|
onMount(() => {
|
|
332
|
+
if (props.surface === "modal") {
|
|
333
|
+
const key = (event: KeyboardEvent) => {
|
|
334
|
+
if (event.defaultPrevented || event.isComposing) return;
|
|
335
|
+
const target = event.target;
|
|
336
|
+
if (event.key === "Escape" && target === searchInput && query()) {
|
|
337
|
+
event.preventDefault();
|
|
338
|
+
changeQuery("");
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
// Some layouts type "/" with Shift (German: Shift+7), so Shift does not disqualify it.
|
|
342
|
+
if (event.ctrlKey || event.metaKey || event.altKey) return;
|
|
343
|
+
if (
|
|
344
|
+
event.key === "/" &&
|
|
345
|
+
!(target instanceof Element && target.closest('input, textarea, select, [contenteditable]:not([contenteditable="false"])'))
|
|
346
|
+
) {
|
|
347
|
+
event.preventDefault();
|
|
348
|
+
searchInput?.focus();
|
|
349
|
+
}
|
|
350
|
+
};
|
|
351
|
+
document.addEventListener("keydown", key);
|
|
352
|
+
onCleanup(() => document.removeEventListener("keydown", key));
|
|
353
|
+
}
|
|
365
354
|
const dialog = root?.closest("dialog");
|
|
366
355
|
dialog?.setAttribute("aria-labelledby", "layout-help-title");
|
|
367
|
-
const update = () => setExternalTopics(mergeTopics(
|
|
356
|
+
const update = () => setExternalTopics(mergeTopics(registeredTopics(), props.documents));
|
|
368
357
|
window.addEventListener(HELP_TOPICS_EVENT, update);
|
|
369
358
|
update();
|
|
370
359
|
onCleanup(() => window.removeEventListener(HELP_TOPICS_EVENT, update));
|
|
@@ -452,16 +441,19 @@ const HelpShell = (props: {
|
|
|
452
441
|
loadAttempt();
|
|
453
442
|
const topic = activeTopic();
|
|
454
443
|
const version = ++requestVersion;
|
|
455
|
-
|
|
444
|
+
const cached = topic && view() === "article" ? props.session.articleCache.get(`${topic.locale ?? "en"}:${topic.url}`) : undefined;
|
|
456
445
|
setLoadError(null);
|
|
457
446
|
setLoading(false);
|
|
458
|
-
|
|
459
|
-
const cached = props.session.articleCache.get(`${topic.locale ?? "en"}:${topic.url}`);
|
|
447
|
+
// Keep a seeded payload in place on mount: no empty frame or duplicate scroll restoration.
|
|
460
448
|
if (cached) {
|
|
461
|
-
|
|
462
|
-
|
|
449
|
+
if (untrack(payload) !== cached) {
|
|
450
|
+
setPayload(cached);
|
|
451
|
+
restoreArticleScroll();
|
|
452
|
+
}
|
|
463
453
|
return;
|
|
464
454
|
}
|
|
455
|
+
setPayload(null);
|
|
456
|
+
if (!topic || view() !== "article") return;
|
|
465
457
|
|
|
466
458
|
const controller = new AbortController();
|
|
467
459
|
setLoading(true);
|
|
@@ -629,99 +621,6 @@ const HelpShell = (props: {
|
|
|
629
621
|
}
|
|
630
622
|
};
|
|
631
623
|
|
|
632
|
-
/** Rows: icon tile, title, and description; the start's groups, its further tasks, and search results use them. */
|
|
633
|
-
const TopicList = (listProps: { items: HelpTopic[]; marker?: string }) => (
|
|
634
|
-
<ul class="help-start-rows" {...(listProps.marker ? { [listProps.marker]: "" } : {})}>
|
|
635
|
-
<For each={listProps.items}>
|
|
636
|
-
{(topic) => (
|
|
637
|
-
<li>
|
|
638
|
-
<button type="button" class="help-start-row focus-ui" data-help-topic={topic.id} onClick={() => openTopic(topic.id)}>
|
|
639
|
-
<span class="help-topic-icon help-start-row-icon app-accent-text" aria-hidden="true">
|
|
640
|
-
<i class={iconClass(topic.icon)} />
|
|
641
|
-
</span>
|
|
642
|
-
<span class="help-article-link-copy">
|
|
643
|
-
<span class="help-article-link-title">{topic.title}</span>
|
|
644
|
-
<Show when={topic.description}>{(description) => <span class="help-article-link-description">{description()}</span>}</Show>
|
|
645
|
-
</span>
|
|
646
|
-
<i class="ti ti-chevron-right help-start-row-arrow" aria-hidden="true" />
|
|
647
|
-
</button>
|
|
648
|
-
</li>
|
|
649
|
-
)}
|
|
650
|
-
</For>
|
|
651
|
-
</ul>
|
|
652
|
-
);
|
|
653
|
-
|
|
654
|
-
/** At most eight tasks as tiles; the app colour shows only in the icon. */
|
|
655
|
-
const TaskTiles = (tilesProps: { items: HelpTopic[] }) => (
|
|
656
|
-
<ul class="help-start-tiles" data-help-tiles="">
|
|
657
|
-
<For each={tilesProps.items}>
|
|
658
|
-
{(topic) => (
|
|
659
|
-
<li>
|
|
660
|
-
<button type="button" class="help-start-tile focus-ui" data-help-topic={topic.id} onClick={() => openTopic(topic.id)}>
|
|
661
|
-
<span class="help-topic-icon help-start-tile-icon app-accent-text" aria-hidden="true">
|
|
662
|
-
<i class={iconClass(topic.icon)} />
|
|
663
|
-
</span>
|
|
664
|
-
<span class="help-start-tile-title">{topic.title}</span>
|
|
665
|
-
<Show when={topic.description}>{(description) => <span class="help-start-tile-description">{description()}</span>}</Show>
|
|
666
|
-
</button>
|
|
667
|
-
</li>
|
|
668
|
-
)}
|
|
669
|
-
</For>
|
|
670
|
-
</ul>
|
|
671
|
-
);
|
|
672
|
-
|
|
673
|
-
const groupTitle = (kind: HelpStartGroupKind) =>
|
|
674
|
-
({ start: t().groupStart, reference: t().groupReference, troubleshoot: t().groupTroubleshoot })[kind];
|
|
675
|
-
|
|
676
|
-
const ArticleLinkTitle = (link: { link: HelpArticleLink }) => (
|
|
677
|
-
<span class="help-article-link-copy">
|
|
678
|
-
<span class="help-article-link-title">{link.link.title}</span>
|
|
679
|
-
<Show when={link.link.description}>{(description) => <span class="help-article-link-description">{description()}</span>}</Show>
|
|
680
|
-
</span>
|
|
681
|
-
);
|
|
682
|
-
|
|
683
|
-
/** "Next" leads on; related articles keep their own app's colour, and another app says whose article it is. */
|
|
684
|
-
const ArticleEnd = (end: { document: HelpDocumentPayload }) => (
|
|
685
|
-
<Show when={end.document.next || end.document.related.length > 0}>
|
|
686
|
-
<footer class="help-article-end">
|
|
687
|
-
<Show when={end.document.next}>
|
|
688
|
-
{(next) => (
|
|
689
|
-
<a class="help-article-next app-accent-scope focus-ui" href={next().url} style={appAccentStyle(next().accent)}>
|
|
690
|
-
<span class="help-article-next-label">{t().next}</span>
|
|
691
|
-
<span class="help-article-next-row">
|
|
692
|
-
<i class={`${iconClass(next().icon)} help-article-next-icon`} aria-hidden="true" />
|
|
693
|
-
<ArticleLinkTitle link={next()} />
|
|
694
|
-
<i class="ti ti-arrow-right help-article-next-arrow" aria-hidden="true" />
|
|
695
|
-
</span>
|
|
696
|
-
</a>
|
|
697
|
-
)}
|
|
698
|
-
</Show>
|
|
699
|
-
<Show when={end.document.related.length > 0}>
|
|
700
|
-
<section class="help-article-related" aria-labelledby="help-article-related-title">
|
|
701
|
-
<h4 id="help-article-related-title">{t().related}</h4>
|
|
702
|
-
<ul>
|
|
703
|
-
<For each={end.document.related}>
|
|
704
|
-
{(link) => (
|
|
705
|
-
<li>
|
|
706
|
-
<a class="help-article-related-link app-accent-scope focus-ui" href={link.url} style={appAccentStyle(link.accent)}>
|
|
707
|
-
<span class="help-topic-icon help-article-related-icon app-accent-text" aria-hidden="true">
|
|
708
|
-
<i class={iconClass(link.icon)} />
|
|
709
|
-
</span>
|
|
710
|
-
<ArticleLinkTitle link={link} />
|
|
711
|
-
<Show when={link.appId !== appId}>
|
|
712
|
-
<span class="help-article-related-app app-accent-text">{link.appName}</span>
|
|
713
|
-
</Show>
|
|
714
|
-
</a>
|
|
715
|
-
</li>
|
|
716
|
-
)}
|
|
717
|
-
</For>
|
|
718
|
-
</ul>
|
|
719
|
-
</section>
|
|
720
|
-
</Show>
|
|
721
|
-
</footer>
|
|
722
|
-
</Show>
|
|
723
|
-
);
|
|
724
|
-
|
|
725
624
|
const ReaderContent = (content: { children: JSX.Element }) =>
|
|
726
625
|
props.surface === "page" ? (
|
|
727
626
|
<div class="p-4 sm:p-5">{content.children}</div>
|
|
@@ -731,6 +630,8 @@ const HelpShell = (props: {
|
|
|
731
630
|
</ScrollArea>
|
|
732
631
|
);
|
|
733
632
|
|
|
633
|
+
// The dialog copies from its ⋯ menu; only the page repeats the copy action beside the article.
|
|
634
|
+
const copyInMenu = props.surface === "modal";
|
|
734
635
|
const pageHref = () => (props.pageBase ? layoutHelpTopicHref(props.pageBase, view() === "article" ? activeId() : null) : undefined);
|
|
735
636
|
const menuItems = () => [
|
|
736
637
|
{
|
|
@@ -740,7 +641,7 @@ const HelpShell = (props: {
|
|
|
740
641
|
? { disabled: true as const }
|
|
741
642
|
: { action: () => void copyFromMenu() }),
|
|
742
643
|
},
|
|
743
|
-
...(
|
|
644
|
+
...(copyInMenu && pageHref() ? [{ label: t().openAsPage, icon: "ti ti-app-window", href: pageHref()! }] : []),
|
|
744
645
|
];
|
|
745
646
|
|
|
746
647
|
return (
|
|
@@ -754,58 +655,30 @@ const HelpShell = (props: {
|
|
|
754
655
|
: "h-full"
|
|
755
656
|
}`}
|
|
756
657
|
data-surface={props.surface}
|
|
757
|
-
style={appAccentStyle(props.accent)}
|
|
658
|
+
style={[appAccentStyle(props.accent), props.appearance].filter(Boolean).join(";") || undefined}
|
|
758
659
|
>
|
|
759
|
-
{/* One head in every view: the search never moves between the start, an article, and results. An app's embedded
|
|
760
|
-
reader sits in the app's own chrome, which names it and picks its article, so it keeps only the search of its start. */}
|
|
761
660
|
<Show when={props.surface !== "embedded" || view() === "hub"}>
|
|
762
|
-
<
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
<Dropdown.Root label={t().moreActions} position="bottom-left" items={menuItems()}>
|
|
784
|
-
<Dropdown.Trigger iconOnly label={t().moreActions} variant="ghost" data-help-menu="">
|
|
785
|
-
<i class="ti ti-dots" aria-hidden="true" />
|
|
786
|
-
</Dropdown.Trigger>
|
|
787
|
-
</Dropdown.Root>
|
|
788
|
-
<Show when={props.surface === "modal"}>
|
|
789
|
-
<IconButton label={t().closeHelp} title={t().close} onClick={props.close}>
|
|
790
|
-
<i class="ti ti-x" />
|
|
791
|
-
</IconButton>
|
|
792
|
-
</Show>
|
|
793
|
-
</div>
|
|
794
|
-
</div>
|
|
795
|
-
</Show>
|
|
796
|
-
<TextInput
|
|
797
|
-
ref={searchInput}
|
|
798
|
-
type="search"
|
|
799
|
-
class="help-head-search"
|
|
800
|
-
icon="ti ti-search"
|
|
801
|
-
aria-label={t().searchHelp}
|
|
802
|
-
data-help-search=""
|
|
803
|
-
value={query()}
|
|
804
|
-
placeholder={t().searchPlaceholder}
|
|
805
|
-
onValueChange={changeQuery}
|
|
806
|
-
/>
|
|
807
|
-
</div>
|
|
808
|
-
</header>
|
|
661
|
+
<HelpHead
|
|
662
|
+
row={props.surface !== "embedded"}
|
|
663
|
+
back={view() === "article"}
|
|
664
|
+
tile={app?.iconClass ?? "ti ti-help-circle"}
|
|
665
|
+
title={appName ? t().helpFor({ app: appName }) : t().help}
|
|
666
|
+
menu={
|
|
667
|
+
<Dropdown.Root label={t().moreActions} position="bottom-left" items={menuItems()}>
|
|
668
|
+
<Dropdown.Trigger iconOnly label={t().moreActions} variant="ghost" data-help-menu="">
|
|
669
|
+
<i class="ti ti-dots" aria-hidden="true" />
|
|
670
|
+
</Dropdown.Trigger>
|
|
671
|
+
</Dropdown.Root>
|
|
672
|
+
}
|
|
673
|
+
closable={props.surface === "modal"}
|
|
674
|
+
onBack={goBack}
|
|
675
|
+
onClose={props.close}
|
|
676
|
+
query={query()}
|
|
677
|
+
onQuery={changeQuery}
|
|
678
|
+
searchRef={(element) => {
|
|
679
|
+
searchInput = element;
|
|
680
|
+
}}
|
|
681
|
+
/>
|
|
809
682
|
</Show>
|
|
810
683
|
|
|
811
684
|
<ReaderContent>
|
|
@@ -814,101 +687,27 @@ const HelpShell = (props: {
|
|
|
814
687
|
<Show
|
|
815
688
|
when={normalizedQuery()}
|
|
816
689
|
fallback={
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
</Show>
|
|
827
|
-
<Show when={start().tiles.length > 0}>
|
|
828
|
-
<section class="help-start-section" data-help-tasks="" aria-labelledby="help-start-tasks-title">
|
|
829
|
-
<h3 id="help-start-tasks-title" class="help-start-heading">
|
|
830
|
-
<i class="ti ti-checklist" aria-hidden="true" />
|
|
831
|
-
{t().whatToDo}
|
|
832
|
-
</h3>
|
|
833
|
-
<TaskTiles items={start().tiles} />
|
|
834
|
-
<Show when={start().moreTasks.length > 0}>
|
|
835
|
-
<TopicList items={start().moreTasks} marker="data-help-more-tasks" />
|
|
836
|
-
</Show>
|
|
837
|
-
</section>
|
|
838
|
-
</Show>
|
|
839
|
-
<Show when={start().groups.length > 0}>
|
|
840
|
-
<div class="help-start-groups">
|
|
841
|
-
<For each={start().groups}>
|
|
842
|
-
{(group) => (
|
|
843
|
-
<section
|
|
844
|
-
class="help-start-section"
|
|
845
|
-
data-help-group={group.kind}
|
|
846
|
-
aria-labelledby={`help-start-${group.kind}-title`}
|
|
847
|
-
>
|
|
848
|
-
<h3 id={`help-start-${group.kind}-title`} class="help-start-heading">
|
|
849
|
-
<i class={GROUP_ICONS[group.kind]} aria-hidden="true" />
|
|
850
|
-
{groupTitle(group.kind)}
|
|
851
|
-
</h3>
|
|
852
|
-
<TopicList items={group.topics} />
|
|
853
|
-
</section>
|
|
854
|
-
)}
|
|
855
|
-
</For>
|
|
856
|
-
</div>
|
|
857
|
-
</Show>
|
|
858
|
-
{/* An app without Help says so plainly; only a failed read of its Help offers a way to try again. */}
|
|
859
|
-
<Show when={topics().length === 0}>
|
|
860
|
-
<Show
|
|
861
|
-
when={shell?.helpFailed}
|
|
862
|
-
fallback={<Placeholder icon="ti ti-book-off" title={t().noArticles} description={t().noArticlesDetail} />}
|
|
863
|
-
>
|
|
864
|
-
<NoticeCard tone="danger" title={t().helpUnavailable} detail={t().helpUnavailableDetail} data-help-unavailable="">
|
|
865
|
-
<Button size="sm" variant="secondary" onClick={() => location.reload()}>
|
|
866
|
-
{t().reload}
|
|
867
|
-
</Button>
|
|
868
|
-
</NoticeCard>
|
|
690
|
+
<HelpStartView
|
|
691
|
+
start={start()}
|
|
692
|
+
empty={topics().length === 0}
|
|
693
|
+
failed={!!shell?.helpFailed}
|
|
694
|
+
onOpen={openTopic}
|
|
695
|
+
onReload={() => location.reload()}
|
|
696
|
+
shortcuts={
|
|
697
|
+
<Show when={props.includeShortcuts !== false}>
|
|
698
|
+
<StartShortcuts appName={appName} />
|
|
869
699
|
</Show>
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
<StartShortcuts appName={appName} />
|
|
873
|
-
</Show>
|
|
874
|
-
</>
|
|
700
|
+
}
|
|
701
|
+
/>
|
|
875
702
|
}
|
|
876
703
|
>
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
{
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
</Show>
|
|
885
|
-
<Show when={searchFailed()}>
|
|
886
|
-
<span class="help-start-status-warning" data-help-search-failed="" title={t().searchTitlesOnly} role="status">
|
|
887
|
-
<i class="ti ti-alert-triangle" aria-hidden="true" />
|
|
888
|
-
<span aria-hidden="true">{t().titlesOnly}</span>
|
|
889
|
-
<span class="sr-only">{t().searchTitlesOnly}</span>
|
|
890
|
-
</span>
|
|
891
|
-
<Button
|
|
892
|
-
size="xs"
|
|
893
|
-
variant="ghost"
|
|
894
|
-
class="help-start-status-retry"
|
|
895
|
-
onClick={() => setSearchAttempt((value) => value + 1)}
|
|
896
|
-
>
|
|
897
|
-
{t().tryAgain}
|
|
898
|
-
</Button>
|
|
899
|
-
</Show>
|
|
900
|
-
</div>
|
|
901
|
-
<Show
|
|
902
|
-
when={results().length > 0}
|
|
903
|
-
fallback={
|
|
904
|
-
<Show when={!searching()}>
|
|
905
|
-
<Placeholder icon="ti ti-search-off" title={t().noResults} />
|
|
906
|
-
</Show>
|
|
907
|
-
}
|
|
908
|
-
>
|
|
909
|
-
<TopicList items={results()} />
|
|
910
|
-
</Show>
|
|
911
|
-
</section>
|
|
704
|
+
<HelpResultsView
|
|
705
|
+
results={results()}
|
|
706
|
+
searching={searching()}
|
|
707
|
+
failed={searchFailed()}
|
|
708
|
+
onRetry={() => setSearchAttempt((value) => value + 1)}
|
|
709
|
+
onOpen={openTopic}
|
|
710
|
+
/>
|
|
912
711
|
</Show>
|
|
913
712
|
</div>
|
|
914
713
|
</Show>
|
|
@@ -924,98 +723,56 @@ const HelpShell = (props: {
|
|
|
924
723
|
onCleanup(() => element.removeEventListener("click", followHelpLink));
|
|
925
724
|
}}
|
|
926
725
|
>
|
|
927
|
-
<
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
when={articleParts()?.lead}
|
|
948
|
-
fallback={
|
|
949
|
-
// An article without a leading paragraph, or a built-in topic, shows its description; a loading article shows neither yet.
|
|
950
|
-
<Show when={articleParts() && topic().description}>
|
|
951
|
-
{(description) => <p class="help-article-lead">{description()}</p>}
|
|
952
|
-
</Show>
|
|
953
|
-
}
|
|
726
|
+
<HelpArticleView
|
|
727
|
+
surface={props.surface}
|
|
728
|
+
topic={topic()}
|
|
729
|
+
appName={appName}
|
|
730
|
+
appId={appId}
|
|
731
|
+
parts={articleParts()}
|
|
732
|
+
document={payload()}
|
|
733
|
+
Markdown={MarkdownView}
|
|
734
|
+
showTools={articleSections().length > 0 || !copyInMenu}
|
|
735
|
+
sectionMenu={
|
|
736
|
+
<Show when={articleSections().length > 0}>
|
|
737
|
+
<Dropdown.Root
|
|
738
|
+
label={t().onThisPage}
|
|
739
|
+
position="bottom-left"
|
|
740
|
+
items={articleSections().map((section) => ({
|
|
741
|
+
label: section.label,
|
|
742
|
+
choice: "radio" as const,
|
|
743
|
+
checked: () => activeSectionId() === section.id,
|
|
744
|
+
action: () => openArticleSection(section),
|
|
745
|
+
}))}
|
|
954
746
|
>
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
</Show>
|
|
978
|
-
</ul>
|
|
979
|
-
</Show>
|
|
980
|
-
<Show when={articleSections().length > 0 || props.surface !== "modal"}>
|
|
981
|
-
<div class="help-article-tools">
|
|
982
|
-
<Show when={articleSections().length > 0}>
|
|
983
|
-
<Dropdown.Root
|
|
984
|
-
label={t().onThisPage}
|
|
985
|
-
position="bottom-left"
|
|
986
|
-
items={articleSections().map((section) => ({
|
|
987
|
-
label: section.label,
|
|
988
|
-
choice: "radio" as const,
|
|
989
|
-
checked: () => activeSectionId() === section.id,
|
|
990
|
-
action: () => openArticleSection(section),
|
|
991
|
-
}))}
|
|
992
|
-
>
|
|
993
|
-
<Dropdown.Trigger label={t().onThisPage} size="sm" tooltip={false} variant="secondary">
|
|
994
|
-
<i class="ti ti-list" aria-hidden="true" />
|
|
995
|
-
<span>{t().onThisPage}</span>
|
|
996
|
-
<i class="ti ti-chevron-down" aria-hidden="true" />
|
|
997
|
-
</Dropdown.Trigger>
|
|
998
|
-
</Dropdown.Root>
|
|
999
|
-
</Show>
|
|
1000
|
-
<Show when={props.surface !== "modal"}>
|
|
1001
|
-
<IconButton
|
|
1002
|
-
class="help-article-tools-copy"
|
|
1003
|
-
disabled={!payload()}
|
|
1004
|
-
label={articleClipboard.wasCopied() ? t().articleCopied : t().copyArticle}
|
|
1005
|
-
title={articleClipboard.wasCopied() ? t().copied : t().copyArticle}
|
|
1006
|
-
onClick={() => void copyArticle(topic(), payload())}
|
|
1007
|
-
>
|
|
1008
|
-
<i class={articleClipboard.wasCopied() ? "ti ti-check" : "ti ti-markdown"} />
|
|
1009
|
-
</IconButton>
|
|
1010
|
-
</Show>
|
|
1011
|
-
</div>
|
|
1012
|
-
</Show>
|
|
1013
|
-
</header>
|
|
747
|
+
<Dropdown.Trigger label={t().onThisPage} size="sm" tooltip={false} variant="secondary">
|
|
748
|
+
<i class="ti ti-list" aria-hidden="true" />
|
|
749
|
+
<span>{t().onThisPage}</span>
|
|
750
|
+
<i class="ti ti-chevron-down" aria-hidden="true" />
|
|
751
|
+
</Dropdown.Trigger>
|
|
752
|
+
</Dropdown.Root>
|
|
753
|
+
</Show>
|
|
754
|
+
}
|
|
755
|
+
copyButton={
|
|
756
|
+
<Show when={!copyInMenu}>
|
|
757
|
+
<IconButton
|
|
758
|
+
class="help-article-tools-copy"
|
|
759
|
+
disabled={!payload()}
|
|
760
|
+
label={articleClipboard.wasCopied() ? t().articleCopied : t().copyArticle}
|
|
761
|
+
title={articleClipboard.wasCopied() ? t().copied : t().copyArticle}
|
|
762
|
+
onClick={() => void copyArticle(topic(), payload())}
|
|
763
|
+
>
|
|
764
|
+
<i class={articleClipboard.wasCopied() ? "ti ti-check" : "ti ti-markdown"} />
|
|
765
|
+
</IconButton>
|
|
766
|
+
</Show>
|
|
767
|
+
}
|
|
768
|
+
loading={
|
|
1014
769
|
<Show when={loading()}>
|
|
1015
770
|
<div class="min-h-[24rem]">
|
|
1016
771
|
<Placeholder state="loading" variant="panel" title={t().loading} />
|
|
1017
772
|
</div>
|
|
1018
773
|
</Show>
|
|
774
|
+
}
|
|
775
|
+
failure={
|
|
1019
776
|
<Show when={loadError()}>
|
|
1020
777
|
{(message) => (
|
|
1021
778
|
<NoticeCard tone="danger" title={t().loadFailed} detail={message()}>
|
|
@@ -1025,29 +782,32 @@ const HelpShell = (props: {
|
|
|
1025
782
|
</NoticeCard>
|
|
1026
783
|
)}
|
|
1027
784
|
</Show>
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
785
|
+
}
|
|
786
|
+
toc={
|
|
787
|
+
<TableOfContents
|
|
788
|
+
items={articleSections()}
|
|
789
|
+
title={t().onThisPage}
|
|
790
|
+
scrollRoot={tocScrollRoot}
|
|
791
|
+
contentRoot={() => articleContent}
|
|
792
|
+
onActiveChange={(id) => setActiveSectionId(id ?? null)}
|
|
793
|
+
/>
|
|
794
|
+
}
|
|
795
|
+
copyAction={
|
|
796
|
+
<Show when={!copyInMenu}>
|
|
797
|
+
<Button size="sm" variant="ghost" disabled={!payload()} onClick={() => void copyArticle(topic(), payload())}>
|
|
798
|
+
<i class={articleClipboard.wasCopied() ? "ti ti-check" : "ti ti-markdown"} />
|
|
799
|
+
{articleClipboard.wasCopied() ? t().copied : t().copyMarkdown}
|
|
800
|
+
</Button>
|
|
801
|
+
</Show>
|
|
802
|
+
}
|
|
803
|
+
headingRef={(element) => {
|
|
804
|
+
articleHeading = element;
|
|
805
|
+
}}
|
|
806
|
+
contentRef={(element) => {
|
|
807
|
+
articleContent = element;
|
|
808
|
+
}}
|
|
809
|
+
onBack={goBack}
|
|
810
|
+
/>
|
|
1051
811
|
</article>
|
|
1052
812
|
)}
|
|
1053
813
|
</Show>
|
|
@@ -1079,14 +839,15 @@ const createSession = (initialTopic?: string): HelpSession => {
|
|
|
1079
839
|
|
|
1080
840
|
/**
|
|
1081
841
|
* Render the shared Help experience as a full page. Apps pass the same
|
|
1082
|
-
* manifest used by `Layout.HelpDocuments`;
|
|
1083
|
-
* from the central Help API.
|
|
842
|
+
* manifest used by `Layout.HelpDocuments`; initialArticle supplies the first
|
|
843
|
+
* article, while other articles load from the central Help API.
|
|
1084
844
|
*/
|
|
1085
845
|
export function LayoutHelpPage(props: LayoutHelpPageProps) {
|
|
1086
846
|
const session = createSession(props.initialTopic);
|
|
1087
847
|
return (
|
|
1088
848
|
<HelpShell
|
|
1089
849
|
session={session}
|
|
850
|
+
initialArticle={props.initialTopic ? props.initialArticle : undefined}
|
|
1090
851
|
close={() => {}}
|
|
1091
852
|
documents={props.documents}
|
|
1092
853
|
includeShortcuts={props.includeShortcuts}
|
|
@@ -1098,12 +859,54 @@ export function LayoutHelpPage(props: LayoutHelpPageProps) {
|
|
|
1098
859
|
);
|
|
1099
860
|
}
|
|
1100
861
|
|
|
1101
|
-
/**
|
|
862
|
+
/** The dialog renders outside the app canvas; it takes the app's canvas colours along for the article head. */
|
|
863
|
+
const canvasAppearance = () => {
|
|
864
|
+
const canvas = document.querySelector(".cloud-app-canvas");
|
|
865
|
+
if (!canvas) return undefined;
|
|
866
|
+
const style = getComputedStyle(canvas);
|
|
867
|
+
return CANVAS_PROPERTIES.flatMap((name) => {
|
|
868
|
+
const value = style.getPropertyValue(name).trim();
|
|
869
|
+
return value ? [`${name}:${value}`] : [];
|
|
870
|
+
}).join(";");
|
|
871
|
+
};
|
|
872
|
+
const CANVAS_PROPERTIES = [
|
|
873
|
+
"--app-canvas-from",
|
|
874
|
+
"--app-canvas-via",
|
|
875
|
+
"--app-canvas-to",
|
|
876
|
+
"--app-canvas-angle",
|
|
877
|
+
"--app-canvas-strength",
|
|
878
|
+
"--app-canvas-dark-strength",
|
|
879
|
+
];
|
|
880
|
+
|
|
881
|
+
/**
|
|
882
|
+
* Open Core's Help provider in the panel slot, or fall back to the current app's dialog when no slot accepts it
|
|
883
|
+
* (anonymous pages, `Layout panel={false}`, an app on an older @k2b/cloud). On a phone the fallback is the shared
|
|
884
|
+
* bottom sheet at full height, like the panel's phone sheet.
|
|
885
|
+
*/
|
|
1102
886
|
export function openLayoutHelpDialog() {
|
|
887
|
+
if (openPanel("core")) return;
|
|
1103
888
|
const session = createSession();
|
|
1104
889
|
const accent = readShellData()?.accent;
|
|
890
|
+
const appearance = canvasAppearance();
|
|
891
|
+
const pageBase = window.__cloudLayoutHelpPageBase;
|
|
892
|
+
if (window.matchMedia("(max-width: 47.999rem)").matches) {
|
|
893
|
+
void dialogCore.open<void>(
|
|
894
|
+
(close, context) => (
|
|
895
|
+
<BottomSheet onDismiss={context.requestDismiss}>
|
|
896
|
+
<HelpShell session={session} close={() => close()} accent={accent} appearance={appearance} surface="modal" pageBase={pageBase} />
|
|
897
|
+
</BottomSheet>
|
|
898
|
+
),
|
|
899
|
+
{
|
|
900
|
+
...bottomSheetOptions,
|
|
901
|
+
panelClassName: `${bottomSheetOptions.panelClassName} cloud-help-sheet`,
|
|
902
|
+
ariaLabel: helpMessages.resolve([document.documentElement.lang || "en"]).t.help,
|
|
903
|
+
history: true,
|
|
904
|
+
},
|
|
905
|
+
);
|
|
906
|
+
return;
|
|
907
|
+
}
|
|
1105
908
|
void prompts.dialog<void>(
|
|
1106
|
-
(close) => <HelpShell session={session} close={close} accent={accent} surface="modal" pageBase={
|
|
909
|
+
(close) => <HelpShell session={session} close={close} accent={accent} appearance={appearance} surface="modal" pageBase={pageBase} />,
|
|
1107
910
|
{ surface: "bare", header: false, size: "wide" },
|
|
1108
911
|
);
|
|
1109
912
|
}
|