@k2b/cloud 0.40.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/package.json +6 -3
- package/src/_internal/capability-sentences.ts +2 -1
- package/src/_internal/define-app.ts +13 -3
- package/src/_internal/heartbeat.ts +203 -14
- package/src/_internal/registry.ts +28 -1
- package/src/_internal/setup-lock.ts +103 -0
- package/src/ai/chat/action-card.ts +69 -3
- package/src/ai/chat/action-question.tsx +12 -1
- package/src/ai/chat/action-text.ts +288 -0
- package/src/ai/chat/tool-action-words.ts +31 -0
- package/src/ai/chat/tool-action.tsx +1 -26
- package/src/ai/executor.ts +10 -2
- package/src/ai/firecrawl-tools.ts +15 -2
- package/src/ai/index.ts +1 -0
- package/src/ai/migrate.ts +15 -0
- package/src/ai/open-questions.ts +172 -0
- package/src/ai/question-text.ts +8 -0
- package/src/ai/routes.ts +51 -5
- package/src/ai/store.ts +18 -0
- package/src/ai/testing.ts +36 -0
- package/src/ai/tool-sentences.ts +152 -34
- package/src/ai/tool-wording.ts +12 -6
- package/src/ai/tools.ts +8 -3
- package/src/ai/types.ts +16 -2
- package/src/api/app-approval-actions.ts +31 -7
- package/src/api/auth/schemas.ts +3 -0
- package/src/api/auth.ts +54 -18
- package/src/api/mcp.ts +27 -3
- package/src/api/me.ts +79 -3
- package/src/approvals/Approvals.tsx +25 -63
- package/src/approvals/approval-card.ts +68 -0
- package/src/contracts/app-approval.ts +3 -3
- package/src/contracts/capabilities.ts +1 -1
- package/src/contracts/shared.ts +3 -0
- package/src/server/middleware/auth.ts +4 -2
- package/src/services/accounts/app.ts +23 -3
- package/src/services/accounts/users.ts +12 -1
- package/src/services/approvals/device.ts +49 -33
- package/src/services/approvals/index.ts +1 -0
- package/src/services/approvals/list.ts +1 -1
- 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/password-reset.ts +32 -6
- package/src/services/index.ts +2 -0
- package/src/services/pdf/markdown.ts +160 -2
- package/src/services/session/index.ts +203 -29
- 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/LayoutHelp.tsx +182 -540
- package/src/ssr/LayoutHelpPanel.browser-harness.tsx +6 -7
- package/src/ssr/LayoutHelpPanel.server.browser-harness.tsx +131 -0
- package/src/ssr/LayoutHelpPanel.server.tsx +115 -0
- package/src/ssr/__snapshots__/LayoutHelp.ssr.render.test.tsx.snap +29 -0
- package/src/ssr/help.ts +4 -3
- package/src/ssr/index.ts +1 -0
- package/src/ssr/islands/index.ts +3 -4
- 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-views.tsx +484 -0
- package/src/ssr/panel-idle.ts +9 -0
- package/src/styles/approvals.css +6 -0
- package/src/styles/effects.css +15 -0
package/src/ssr/LayoutHelp.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
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
4
|
BottomSheet,
|
|
@@ -7,8 +7,6 @@ import {
|
|
|
7
7
|
Dropdown,
|
|
8
8
|
dialogCore,
|
|
9
9
|
IconButton,
|
|
10
|
-
Kbd,
|
|
11
|
-
type KeyboardPlatform,
|
|
12
10
|
MarkdownView,
|
|
13
11
|
NoticeCard,
|
|
14
12
|
Placeholder,
|
|
@@ -16,14 +14,12 @@ import {
|
|
|
16
14
|
prompts,
|
|
17
15
|
ScrollArea,
|
|
18
16
|
TableOfContents,
|
|
19
|
-
TextInput,
|
|
20
17
|
toast,
|
|
21
18
|
useLocale,
|
|
22
19
|
} from "@k2b/ui";
|
|
23
|
-
import { createEffect, createMemo, createSignal,
|
|
20
|
+
import { createEffect, createMemo, createSignal, type JSX, onCleanup, onMount, Show, untrack } from "solid-js";
|
|
24
21
|
import { COMMANDS_CHANGED, contextCommandsWithShortcuts } from "../browser/command-shortcuts";
|
|
25
|
-
import {
|
|
26
|
-
import { onPanelShortcutsChange, panelShortcutsReceived, readPanelShortcuts } from "../browser/panel-shortcuts";
|
|
22
|
+
import { openPanel } from "../browser/panel";
|
|
27
23
|
import { HELP_ACCESS_LEVELS, HELP_KINDS } from "../shared/help";
|
|
28
24
|
import { appAccentStyle } from "./app-appearance";
|
|
29
25
|
import type { LayoutHelpArticle } from "./help";
|
|
@@ -33,14 +29,9 @@ import { formatHelpBundleMarkdown, formatHelpDocumentMarkdown } from "./layout-h
|
|
|
33
29
|
import { focusHelpArticleHeading, resetHelpArticleScroll } from "./layout-help-navigation";
|
|
34
30
|
import { HELP_TOPICS_EVENT, type HelpTopic, registry } from "./layout-help-registry";
|
|
35
31
|
import { searchHelpTopics } from "./layout-help-search";
|
|
36
|
-
import {
|
|
37
|
-
type HelpShortcutCommand,
|
|
38
|
-
type HelpShortcutScope,
|
|
39
|
-
type HelpStartGroupKind,
|
|
40
|
-
helpShortcutGroups,
|
|
41
|
-
helpStart,
|
|
42
|
-
} from "./layout-help-start";
|
|
32
|
+
import { type HelpShortcutCommand, helpShortcutGroups, helpStart } from "./layout-help-start";
|
|
43
33
|
import { helpLinkTarget, layoutHelpTopicHref } from "./layout-help-url";
|
|
34
|
+
import { HelpArticleView, HelpHead, HelpResultsView, HelpShortcutsView, HelpStartView, type HelpSurface } from "./layout-help-views";
|
|
44
35
|
import { platformMessages } from "./platform-messages";
|
|
45
36
|
import { readShellData, useShellData } from "./shell-data";
|
|
46
37
|
import { shellShortcuts } from "./shell-shortcuts";
|
|
@@ -58,22 +49,6 @@ export type LayoutHelpPageProps = {
|
|
|
58
49
|
pageBase?: string;
|
|
59
50
|
};
|
|
60
51
|
|
|
61
|
-
export type LayoutHelpPanelProps = {
|
|
62
|
-
documents: readonly HelpDocumentManifest[];
|
|
63
|
-
/** Absent when the context selected Help the reader may not see. */
|
|
64
|
-
app?: { id: string; name: string; icon?: string };
|
|
65
|
-
/** appAppearanceStyle() of the app, from the server. */
|
|
66
|
-
appearance?: string;
|
|
67
|
-
/** Server-filtered For this view ids. */
|
|
68
|
-
view?: readonly string[];
|
|
69
|
-
initialTopic?: string;
|
|
70
|
-
/** Authorized article and server-parsed parts for an identical first frame. */
|
|
71
|
-
initialArticle?: LayoutHelpArticle;
|
|
72
|
-
pageBase?: string;
|
|
73
|
-
platform: KeyboardPlatform;
|
|
74
|
-
state?: "failed" | "unavailable";
|
|
75
|
-
};
|
|
76
|
-
|
|
77
52
|
type HelpView = "hub" | "article";
|
|
78
53
|
type HelpSession = {
|
|
79
54
|
view: HelpView;
|
|
@@ -101,8 +76,6 @@ const articleLink = (value: unknown): HelpArticleLink | undefined => {
|
|
|
101
76
|
return { appId, appName, accent, documentId, title, description, icon, url };
|
|
102
77
|
};
|
|
103
78
|
|
|
104
|
-
/** An article without its own icon gets a neutral page tile. */
|
|
105
|
-
const iconClass = (icon?: string) => (icon?.startsWith("ti ") ? icon : `ti ${icon ?? "ti-file-text"}`);
|
|
106
79
|
const byOrder = (a: HelpTopic, b: HelpTopic) => (a.order ?? 100) - (b.order ?? 100) || a.title.localeCompare(b.title);
|
|
107
80
|
const sortedTopics = () => [...(registry()?.values() ?? [])].sort(byOrder);
|
|
108
81
|
const mergeTopics = (registered: readonly HelpTopic[], documents: readonly HelpDocumentManifest[] = []): HelpTopic[] => {
|
|
@@ -110,11 +83,6 @@ const mergeTopics = (registered: readonly HelpTopic[], documents: readonly HelpD
|
|
|
110
83
|
for (const topic of documents) topics.set(topic.id, topic);
|
|
111
84
|
return [...topics.values()].sort(byOrder);
|
|
112
85
|
};
|
|
113
|
-
const GROUP_ICONS: Record<HelpStartGroupKind, string> = {
|
|
114
|
-
start: "ti ti-rocket",
|
|
115
|
-
reference: "ti ti-book-2",
|
|
116
|
-
troubleshoot: "ti ti-lifebuoy",
|
|
117
|
-
};
|
|
118
86
|
|
|
119
87
|
/**
|
|
120
88
|
* Keyboard shortcuts come last in the start. The browser knows the page's commands. On a Layout page (it has shell
|
|
@@ -122,68 +90,22 @@ const GROUP_ICONS: Record<HelpStartGroupKind, string> = {
|
|
|
122
90
|
* first client render on, also before the shell has registered them. The groups reserve their height and place in CSS,
|
|
123
91
|
* so the ones the browser adds fill space that is already there.
|
|
124
92
|
*/
|
|
125
|
-
const StartShortcuts = (props: { appName?: string
|
|
93
|
+
const StartShortcuts = (props: { appName?: string }) => {
|
|
126
94
|
const locale = useLocale();
|
|
127
|
-
const
|
|
128
|
-
const
|
|
129
|
-
const seed: HelpShortcutCommand[] =
|
|
130
|
-
props.panel || shell ? shellShortcuts(platformMessages.resolve([locale()]).t, { panel: props.panel || !!shell?.panel }) : [];
|
|
95
|
+
const shell = useShellData();
|
|
96
|
+
const seed: HelpShortcutCommand[] = shell ? shellShortcuts(platformMessages.resolve([locale()]).t, { panel: !!shell.panel }) : [];
|
|
131
97
|
const read = (): HelpShortcutCommand[] => {
|
|
132
|
-
// Once received, the host list is authoritative: omitted shortcuts must stay omitted.
|
|
133
|
-
if (props.panel) return panelShortcutsReceived() && getContext() !== null ? readPanelShortcuts() : seed;
|
|
134
98
|
const commands = contextCommandsWithShortcuts();
|
|
135
99
|
return [...seed.filter((command) => !commands.some((other) => other.id === command.id)), ...commands];
|
|
136
100
|
};
|
|
137
|
-
const [commands, setCommands] = createSignal<HelpShortcutCommand[]>(
|
|
101
|
+
const [commands, setCommands] = createSignal<HelpShortcutCommand[]>(read());
|
|
138
102
|
onMount(() => {
|
|
139
103
|
const update = () => setCommands(read());
|
|
140
|
-
if (props.panel) {
|
|
141
|
-
onCleanup(onPanelShortcutsChange(update));
|
|
142
|
-
onCleanup(onContextChange(update));
|
|
143
|
-
}
|
|
144
104
|
update();
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
onCleanup(() => window.removeEventListener(COMMANDS_CHANGED, update));
|
|
148
|
-
}
|
|
105
|
+
window.addEventListener(COMMANDS_CHANGED, update);
|
|
106
|
+
onCleanup(() => window.removeEventListener(COMMANDS_CHANGED, update));
|
|
149
107
|
});
|
|
150
|
-
|
|
151
|
-
scope === "everywhere"
|
|
152
|
-
? t().shortcutsEverywhere
|
|
153
|
-
: scope === "selection"
|
|
154
|
-
? t().shortcutsWithSelection
|
|
155
|
-
: props.appName
|
|
156
|
-
? t().shortcutsInApp({ app: props.appName })
|
|
157
|
-
: t().shortcutsInThisApp;
|
|
158
|
-
return (
|
|
159
|
-
<section class="help-start-section help-start-shortcuts" data-help-shortcuts="" aria-labelledby="help-start-shortcuts-title">
|
|
160
|
-
<h3 id="help-start-shortcuts-title" class="help-start-heading">
|
|
161
|
-
<i class="ti ti-keyboard" aria-hidden="true" />
|
|
162
|
-
{t().keyboardShortcuts}
|
|
163
|
-
</h3>
|
|
164
|
-
<div class="help-shortcut-groups">
|
|
165
|
-
<For each={helpShortcutGroups(commands())}>
|
|
166
|
-
{(group) => (
|
|
167
|
-
<section class="help-shortcut-group" data-help-shortcut-group={group.scope} aria-label={label(group.scope)}>
|
|
168
|
-
<h4 class="help-shortcut-group-title" aria-hidden="true">
|
|
169
|
-
{label(group.scope)}
|
|
170
|
-
</h4>
|
|
171
|
-
<ul>
|
|
172
|
-
<For each={group.shortcuts}>
|
|
173
|
-
{(command) => (
|
|
174
|
-
<li class="help-shortcut" title={command.description}>
|
|
175
|
-
<span class="help-shortcut-title">{command.title}</span>
|
|
176
|
-
<Kbd keys={command.shortcut} platform={props.platform ?? shell?.platform} />
|
|
177
|
-
</li>
|
|
178
|
-
)}
|
|
179
|
-
</For>
|
|
180
|
-
</ul>
|
|
181
|
-
</section>
|
|
182
|
-
)}
|
|
183
|
-
</For>
|
|
184
|
-
</div>
|
|
185
|
-
</section>
|
|
186
|
-
);
|
|
108
|
+
return <HelpShortcutsView groups={helpShortcutGroups(commands())} appName={props.appName} platform={shell?.platform} />;
|
|
187
109
|
};
|
|
188
110
|
|
|
189
111
|
const HelpShell = (props: {
|
|
@@ -196,29 +118,29 @@ const HelpShell = (props: {
|
|
|
196
118
|
accent?: string;
|
|
197
119
|
/** The app's canvas colours for a reader outside the app canvas. */
|
|
198
120
|
appearance?: string;
|
|
199
|
-
surface
|
|
200
|
-
panel?: LayoutHelpPanelProps;
|
|
121
|
+
surface: HelpSurface;
|
|
201
122
|
syncPageUrl?: boolean;
|
|
202
123
|
}) => {
|
|
203
124
|
const locale = useLocale();
|
|
204
125
|
const t = () => helpMessages.resolve([locale()]).t;
|
|
205
126
|
// Links to this app's Help, by its own route or Core's `/help/apps/<id>`, open in place.
|
|
206
|
-
const shell =
|
|
207
|
-
const appId =
|
|
127
|
+
const shell = useShellData();
|
|
128
|
+
const appId = shell?.appId;
|
|
208
129
|
const app = shell?.apps.find((candidate) => candidate.id === appId);
|
|
209
|
-
const appName =
|
|
210
|
-
const
|
|
211
|
-
const registeredTopics = () => (props.surface === "panel" ? [] : sortedTopics());
|
|
212
|
-
const accessLabel = (level: HelpAccessLevel) => ({ view: t().accessView, edit: t().accessEdit, manage: t().accessManage })[level];
|
|
130
|
+
const appName = app?.label;
|
|
131
|
+
const registeredTopics = () => sortedTopics();
|
|
213
132
|
const [externalTopics, setExternalTopics] = createSignal(mergeTopics(registeredTopics(), props.documents));
|
|
214
133
|
const [view, setView] = createSignal<HelpView>(props.session.view);
|
|
215
134
|
const [query, setQuery] = createSignal(props.session.query);
|
|
216
135
|
const [activeId, setActiveId] = createSignal<string | null>(props.session.activeId);
|
|
217
136
|
const initialTopic = props.documents?.find((topic) => topic.id === props.session.activeId);
|
|
218
137
|
const initialArticle = initialTopic && props.initialArticle?.payload.id === initialTopic.id ? props.initialArticle : undefined;
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
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);
|
|
222
144
|
const [loading, setLoading] = createSignal(props.session.view === "article" && !initialArticle);
|
|
223
145
|
const [loadError, setLoadError] = createSignal<string | null>(null);
|
|
224
146
|
const [loadAttempt, setLoadAttempt] = createSignal(0);
|
|
@@ -266,7 +188,7 @@ const HelpShell = (props: {
|
|
|
266
188
|
};
|
|
267
189
|
|
|
268
190
|
const restoreArticleScroll = () => {
|
|
269
|
-
if (
|
|
191
|
+
if (props.surface !== "modal" || pendingNavigation || pendingHash) return;
|
|
270
192
|
requestAnimationFrame(() => {
|
|
271
193
|
if (scrollArea && view() === "article") scrollArea.scrollTop = props.session.articleScrollTop;
|
|
272
194
|
});
|
|
@@ -311,14 +233,12 @@ const HelpShell = (props: {
|
|
|
311
233
|
};
|
|
312
234
|
|
|
313
235
|
const topics = externalTopics;
|
|
314
|
-
const start = createMemo(() =>
|
|
315
|
-
helpStart(topics(), props.surface === "panel" ? viewIds() : props.surface === "modal" ? shell?.help : undefined),
|
|
316
|
-
);
|
|
236
|
+
const start = createMemo(() => helpStart(topics(), props.surface === "modal" ? shell?.help : undefined));
|
|
317
237
|
const activeTopic = createMemo(() => topics().find((topic) => topic.id === activeId()) ?? null);
|
|
318
238
|
// Reuse the serialized parts for the initial object on both sides of the island boundary.
|
|
319
239
|
const articleParts = createMemo(() => {
|
|
320
240
|
const article = payload();
|
|
321
|
-
if (initialArticle && article ===
|
|
241
|
+
if (initialArticle && article === seeded) return initialArticle.parts;
|
|
322
242
|
return article?.html ? helpArticleParts(article.html) : null;
|
|
323
243
|
});
|
|
324
244
|
const articleSections = (): HelpSection[] => (view() === "article" ? articleParts()?.sections : undefined) ?? [];
|
|
@@ -409,10 +329,7 @@ const HelpShell = (props: {
|
|
|
409
329
|
createEffect(syncSession);
|
|
410
330
|
|
|
411
331
|
onMount(() => {
|
|
412
|
-
if (props.surface === "
|
|
413
|
-
onCleanup(onContextChange((context) => setViewIds(context && context.app === appId ? (context.help ?? []) : [])));
|
|
414
|
-
}
|
|
415
|
-
if (props.surface === "panel" || props.surface === "modal") {
|
|
332
|
+
if (props.surface === "modal") {
|
|
416
333
|
const key = (event: KeyboardEvent) => {
|
|
417
334
|
if (event.defaultPrevented || event.isComposing) return;
|
|
418
335
|
const target = event.target;
|
|
@@ -445,7 +362,7 @@ const HelpShell = (props: {
|
|
|
445
362
|
const onScroll = () => {
|
|
446
363
|
if (view() === "article" && scrollArea) props.session.articleScrollTop = scrollArea.scrollTop;
|
|
447
364
|
};
|
|
448
|
-
if (props.surface === "modal"
|
|
365
|
+
if (props.surface === "modal") scrollArea?.addEventListener("scroll", onScroll, { passive: true });
|
|
449
366
|
// Layout's breakpoints move page scrolling between its main column and the window; the contents follow.
|
|
450
367
|
let resizeFrame: number | undefined;
|
|
451
368
|
const onResize = () => {
|
|
@@ -704,99 +621,6 @@ const HelpShell = (props: {
|
|
|
704
621
|
}
|
|
705
622
|
};
|
|
706
623
|
|
|
707
|
-
/** Rows: icon tile, title, and description; the start's groups, its further tasks, and search results use them. */
|
|
708
|
-
const TopicList = (listProps: { items: HelpTopic[]; marker?: string }) => (
|
|
709
|
-
<ul class="help-start-rows" {...(listProps.marker ? { [listProps.marker]: "" } : {})}>
|
|
710
|
-
<For each={listProps.items}>
|
|
711
|
-
{(topic) => (
|
|
712
|
-
<li>
|
|
713
|
-
<button type="button" class="help-start-row focus-ui" data-help-topic={topic.id} onClick={() => openTopic(topic.id)}>
|
|
714
|
-
<span class="help-topic-icon k2b-icon-tile help-start-row-icon app-accent-text" aria-hidden="true">
|
|
715
|
-
<i class={iconClass(topic.icon)} />
|
|
716
|
-
</span>
|
|
717
|
-
<span class="help-article-link-copy">
|
|
718
|
-
<span class="help-article-link-title">{topic.title}</span>
|
|
719
|
-
<Show when={topic.description}>{(description) => <span class="help-article-link-description">{description()}</span>}</Show>
|
|
720
|
-
</span>
|
|
721
|
-
<i class="ti ti-chevron-right help-start-row-arrow" aria-hidden="true" />
|
|
722
|
-
</button>
|
|
723
|
-
</li>
|
|
724
|
-
)}
|
|
725
|
-
</For>
|
|
726
|
-
</ul>
|
|
727
|
-
);
|
|
728
|
-
|
|
729
|
-
/** At most eight tasks as tiles; the app colour shows only in the icon. */
|
|
730
|
-
const TaskTiles = (tilesProps: { items: HelpTopic[] }) => (
|
|
731
|
-
<ul class="help-start-tiles" data-help-tiles="">
|
|
732
|
-
<For each={tilesProps.items}>
|
|
733
|
-
{(topic) => (
|
|
734
|
-
<li>
|
|
735
|
-
<button type="button" class="help-start-tile focus-ui" data-help-topic={topic.id} onClick={() => openTopic(topic.id)}>
|
|
736
|
-
<span class="help-topic-icon k2b-icon-tile help-start-tile-icon app-accent-text" aria-hidden="true">
|
|
737
|
-
<i class={iconClass(topic.icon)} />
|
|
738
|
-
</span>
|
|
739
|
-
<span class="help-start-tile-title">{topic.title}</span>
|
|
740
|
-
<Show when={topic.description}>{(description) => <span class="help-start-tile-description">{description()}</span>}</Show>
|
|
741
|
-
</button>
|
|
742
|
-
</li>
|
|
743
|
-
)}
|
|
744
|
-
</For>
|
|
745
|
-
</ul>
|
|
746
|
-
);
|
|
747
|
-
|
|
748
|
-
const groupTitle = (kind: HelpStartGroupKind) =>
|
|
749
|
-
({ start: t().groupStart, reference: t().groupReference, troubleshoot: t().groupTroubleshoot })[kind];
|
|
750
|
-
|
|
751
|
-
const ArticleLinkTitle = (link: { link: HelpArticleLink }) => (
|
|
752
|
-
<span class="help-article-link-copy">
|
|
753
|
-
<span class="help-article-link-title">{link.link.title}</span>
|
|
754
|
-
<Show when={link.link.description}>{(description) => <span class="help-article-link-description">{description()}</span>}</Show>
|
|
755
|
-
</span>
|
|
756
|
-
);
|
|
757
|
-
|
|
758
|
-
/** "Next" leads on; related articles keep their own app's colour, and another app says whose article it is. */
|
|
759
|
-
const ArticleEnd = (end: { document: HelpDocumentPayload }) => (
|
|
760
|
-
<Show when={end.document.next || end.document.related.length > 0}>
|
|
761
|
-
<footer class="help-article-end">
|
|
762
|
-
<Show when={end.document.next}>
|
|
763
|
-
{(next) => (
|
|
764
|
-
<a class="help-article-next app-accent-scope focus-ui" href={next().url} style={appAccentStyle(next().accent)}>
|
|
765
|
-
<span class="help-article-next-label">{t().next}</span>
|
|
766
|
-
<span class="help-article-next-row">
|
|
767
|
-
<i class={`${iconClass(next().icon)} help-article-next-icon`} aria-hidden="true" />
|
|
768
|
-
<ArticleLinkTitle link={next()} />
|
|
769
|
-
<i class="ti ti-arrow-right help-article-next-arrow" aria-hidden="true" />
|
|
770
|
-
</span>
|
|
771
|
-
</a>
|
|
772
|
-
)}
|
|
773
|
-
</Show>
|
|
774
|
-
<Show when={end.document.related.length > 0}>
|
|
775
|
-
<section class="help-article-related" aria-labelledby="help-article-related-title">
|
|
776
|
-
<h4 id="help-article-related-title">{t().related}</h4>
|
|
777
|
-
<ul>
|
|
778
|
-
<For each={end.document.related}>
|
|
779
|
-
{(link) => (
|
|
780
|
-
<li>
|
|
781
|
-
<a class="help-article-related-link app-accent-scope focus-ui" href={link.url} style={appAccentStyle(link.accent)}>
|
|
782
|
-
<span class="help-topic-icon k2b-icon-tile help-article-related-icon app-accent-text" aria-hidden="true">
|
|
783
|
-
<i class={iconClass(link.icon)} />
|
|
784
|
-
</span>
|
|
785
|
-
<ArticleLinkTitle link={link} />
|
|
786
|
-
<Show when={link.appId !== appId}>
|
|
787
|
-
<span class="help-article-related-app app-accent-text">{link.appName}</span>
|
|
788
|
-
</Show>
|
|
789
|
-
</a>
|
|
790
|
-
</li>
|
|
791
|
-
)}
|
|
792
|
-
</For>
|
|
793
|
-
</ul>
|
|
794
|
-
</section>
|
|
795
|
-
</Show>
|
|
796
|
-
</footer>
|
|
797
|
-
</Show>
|
|
798
|
-
);
|
|
799
|
-
|
|
800
624
|
const ReaderContent = (content: { children: JSX.Element }) =>
|
|
801
625
|
props.surface === "page" ? (
|
|
802
626
|
<div class="p-4 sm:p-5">{content.children}</div>
|
|
@@ -806,8 +630,8 @@ const HelpShell = (props: {
|
|
|
806
630
|
</ScrollArea>
|
|
807
631
|
);
|
|
808
632
|
|
|
809
|
-
// The dialog
|
|
810
|
-
const copyInMenu = props.surface === "modal"
|
|
633
|
+
// The dialog copies from its ⋯ menu; only the page repeats the copy action beside the article.
|
|
634
|
+
const copyInMenu = props.surface === "modal";
|
|
811
635
|
const pageHref = () => (props.pageBase ? layoutHelpTopicHref(props.pageBase, view() === "article" ? activeId() : null) : undefined);
|
|
812
636
|
const menuItems = () => [
|
|
813
637
|
{
|
|
@@ -823,335 +647,169 @@ const HelpShell = (props: {
|
|
|
823
647
|
return (
|
|
824
648
|
<div
|
|
825
649
|
ref={root}
|
|
826
|
-
class={
|
|
827
|
-
props.surface === "
|
|
828
|
-
? "
|
|
829
|
-
:
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
? ""
|
|
834
|
-
: "h-full"
|
|
835
|
-
}`
|
|
836
|
-
}
|
|
650
|
+
class={`help-shell app-accent-scope flex min-h-0 flex-col ${props.surface === "page" ? "" : "bg-[var(--ui-surface-raised)]"} ${
|
|
651
|
+
props.surface === "modal"
|
|
652
|
+
? "h-[min(48rem,var(--ui-dialog-available-height))] overflow-hidden panel-dialog-shell [box-shadow:var(--ui-shadow-float)]"
|
|
653
|
+
: props.surface === "page"
|
|
654
|
+
? ""
|
|
655
|
+
: "h-full"
|
|
656
|
+
}`}
|
|
837
657
|
data-surface={props.surface}
|
|
838
|
-
style={
|
|
839
|
-
props.surface === "panel"
|
|
840
|
-
? props.panel?.appearance
|
|
841
|
-
: [appAccentStyle(props.accent), props.appearance].filter(Boolean).join(";") || undefined
|
|
842
|
-
}
|
|
658
|
+
style={[appAccentStyle(props.accent), props.appearance].filter(Boolean).join(";") || undefined}
|
|
843
659
|
>
|
|
844
|
-
{
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
<
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
{appName ? (props.surface === "panel" ? appName : t().helpFor({ app: appName })) : t().help}
|
|
867
|
-
</h2>
|
|
868
|
-
<div class="help-head-actions" role="group" aria-label={t().actions}>
|
|
869
|
-
<Dropdown.Root label={t().moreActions} position="bottom-left" items={menuItems()}>
|
|
870
|
-
<Dropdown.Trigger iconOnly label={t().moreActions} variant="ghost" data-help-menu="">
|
|
871
|
-
<i class="ti ti-dots" aria-hidden="true" />
|
|
872
|
-
</Dropdown.Trigger>
|
|
873
|
-
</Dropdown.Root>
|
|
874
|
-
<Show when={props.surface === "modal"}>
|
|
875
|
-
<IconButton label={t().closeHelp} title={t().close} onClick={props.close}>
|
|
876
|
-
<i class="ti ti-x" />
|
|
877
|
-
</IconButton>
|
|
878
|
-
</Show>
|
|
879
|
-
</div>
|
|
880
|
-
</div>
|
|
881
|
-
</Show>
|
|
882
|
-
<TextInput
|
|
883
|
-
ref={searchInput}
|
|
884
|
-
type="search"
|
|
885
|
-
class="help-head-search"
|
|
886
|
-
icon="ti ti-search"
|
|
887
|
-
aria-label={t().searchHelp}
|
|
888
|
-
data-help-search=""
|
|
889
|
-
value={query()}
|
|
890
|
-
placeholder={t().searchPlaceholder}
|
|
891
|
-
onValueChange={changeQuery}
|
|
892
|
-
/>
|
|
893
|
-
</div>
|
|
894
|
-
</header>
|
|
660
|
+
<Show when={props.surface !== "embedded" || view() === "hub"}>
|
|
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
|
+
/>
|
|
895
682
|
</Show>
|
|
896
683
|
|
|
897
684
|
<ReaderContent>
|
|
898
|
-
<Show
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
<h3 id="help-start-view-title" class="help-start-heading">
|
|
911
|
-
<i class="ti ti-focus-2" aria-hidden="true" />
|
|
912
|
-
{t().forThisView}
|
|
913
|
-
</h3>
|
|
914
|
-
<TopicList items={start().view} />
|
|
915
|
-
</section>
|
|
916
|
-
</Show>
|
|
917
|
-
<Show when={start().tiles.length > 0}>
|
|
918
|
-
<section class="help-start-section" data-help-tasks="" aria-labelledby="help-start-tasks-title">
|
|
919
|
-
<h3 id="help-start-tasks-title" class="help-start-heading">
|
|
920
|
-
<i class="ti ti-checklist" aria-hidden="true" />
|
|
921
|
-
{t().whatToDo}
|
|
922
|
-
</h3>
|
|
923
|
-
<TaskTiles items={start().tiles} />
|
|
924
|
-
<Show when={start().moreTasks.length > 0}>
|
|
925
|
-
<TopicList items={start().moreTasks} marker="data-help-more-tasks" />
|
|
926
|
-
</Show>
|
|
927
|
-
</section>
|
|
928
|
-
</Show>
|
|
929
|
-
<Show when={start().groups.length > 0}>
|
|
930
|
-
<div class="help-start-groups">
|
|
931
|
-
<For each={start().groups}>
|
|
932
|
-
{(group) => (
|
|
933
|
-
<section
|
|
934
|
-
class="help-start-section"
|
|
935
|
-
data-help-group={group.kind}
|
|
936
|
-
aria-labelledby={`help-start-${group.kind}-title`}
|
|
937
|
-
>
|
|
938
|
-
<h3 id={`help-start-${group.kind}-title`} class="help-start-heading">
|
|
939
|
-
<i class={GROUP_ICONS[group.kind]} aria-hidden="true" />
|
|
940
|
-
{groupTitle(group.kind)}
|
|
941
|
-
</h3>
|
|
942
|
-
<TopicList items={group.topics} />
|
|
943
|
-
</section>
|
|
944
|
-
)}
|
|
945
|
-
</For>
|
|
946
|
-
</div>
|
|
947
|
-
</Show>
|
|
948
|
-
{/* An app without Help says so plainly; only a failed read of its Help offers a way to try again. */}
|
|
949
|
-
<Show when={topics().length === 0}>
|
|
950
|
-
<Show
|
|
951
|
-
when={props.panel?.state === "failed" || shell?.helpFailed}
|
|
952
|
-
fallback={<Placeholder icon="ti ti-book-off" title={t().noArticles} description={t().noArticlesDetail} />}
|
|
953
|
-
>
|
|
954
|
-
<NoticeCard tone="danger" title={t().helpUnavailable} detail={t().helpUnavailableDetail} data-help-unavailable="">
|
|
955
|
-
<Button size="sm" variant="secondary" onClick={() => location.reload()}>
|
|
956
|
-
{t().reload}
|
|
957
|
-
</Button>
|
|
958
|
-
</NoticeCard>
|
|
959
|
-
</Show>
|
|
960
|
-
</Show>
|
|
685
|
+
<Show when={view() === "hub"}>
|
|
686
|
+
<div ref={resultsRegion} data-help-results="" class="help-start help-measure" style={{ "min-height": resultsMinHeight() }}>
|
|
687
|
+
<Show
|
|
688
|
+
when={normalizedQuery()}
|
|
689
|
+
fallback={
|
|
690
|
+
<HelpStartView
|
|
691
|
+
start={start()}
|
|
692
|
+
empty={topics().length === 0}
|
|
693
|
+
failed={!!shell?.helpFailed}
|
|
694
|
+
onOpen={openTopic}
|
|
695
|
+
onReload={() => location.reload()}
|
|
696
|
+
shortcuts={
|
|
961
697
|
<Show when={props.includeShortcuts !== false}>
|
|
962
|
-
<StartShortcuts appName={appName}
|
|
698
|
+
<StartShortcuts appName={appName} />
|
|
963
699
|
</Show>
|
|
964
|
-
|
|
700
|
+
}
|
|
701
|
+
/>
|
|
702
|
+
}
|
|
703
|
+
>
|
|
704
|
+
<HelpResultsView
|
|
705
|
+
results={results()}
|
|
706
|
+
searching={searching()}
|
|
707
|
+
failed={searchFailed()}
|
|
708
|
+
onRetry={() => setSearchAttempt((value) => value + 1)}
|
|
709
|
+
onOpen={openTopic}
|
|
710
|
+
/>
|
|
711
|
+
</Show>
|
|
712
|
+
</div>
|
|
713
|
+
</Show>
|
|
714
|
+
|
|
715
|
+
<Show when={view() === "article" && activeTopic()}>
|
|
716
|
+
{(topic) => (
|
|
717
|
+
<article
|
|
718
|
+
class="help-article"
|
|
719
|
+
data-surface={props.surface}
|
|
720
|
+
ref={(element) => {
|
|
721
|
+
// Delegated from the article: its body, table of contents, and end hold the links.
|
|
722
|
+
element.addEventListener("click", followHelpLink);
|
|
723
|
+
onCleanup(() => element.removeEventListener("click", followHelpLink));
|
|
724
|
+
}}
|
|
725
|
+
>
|
|
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
|
+
}))}
|
|
746
|
+
>
|
|
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>
|
|
965
754
|
}
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
<i class="ti ti-alert-triangle" aria-hidden="true" />
|
|
978
|
-
<span aria-hidden="true">{t().titlesOnly}</span>
|
|
979
|
-
<span class="sr-only">{t().searchTitlesOnly}</span>
|
|
980
|
-
</span>
|
|
981
|
-
<Button
|
|
982
|
-
size="xs"
|
|
983
|
-
variant="ghost"
|
|
984
|
-
class="help-start-status-retry"
|
|
985
|
-
onClick={() => setSearchAttempt((value) => value + 1)}
|
|
986
|
-
>
|
|
987
|
-
{t().tryAgain}
|
|
988
|
-
</Button>
|
|
989
|
-
</Show>
|
|
990
|
-
</div>
|
|
991
|
-
<Show
|
|
992
|
-
when={results().length > 0}
|
|
993
|
-
fallback={
|
|
994
|
-
<Show when={!searching()}>
|
|
995
|
-
<Placeholder icon="ti ti-search-off" title={t().noResults} />
|
|
996
|
-
</Show>
|
|
997
|
-
}
|
|
998
|
-
>
|
|
999
|
-
<TopicList items={results()} />
|
|
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>
|
|
1000
766
|
</Show>
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
<Show when={view() === "article" && activeTopic()}>
|
|
1007
|
-
{(topic) => (
|
|
1008
|
-
<article
|
|
1009
|
-
class="help-article"
|
|
1010
|
-
data-surface={props.surface}
|
|
1011
|
-
ref={(element) => {
|
|
1012
|
-
// Delegated from the article: its body, table of contents, and end hold the links.
|
|
1013
|
-
element.addEventListener("click", followHelpLink);
|
|
1014
|
-
onCleanup(() => element.removeEventListener("click", followHelpLink));
|
|
1015
|
-
}}
|
|
1016
|
-
>
|
|
1017
|
-
<div class="help-article-layout">
|
|
1018
|
-
<div class="help-article-main">
|
|
1019
|
-
<header class="help-article-head" data-help-article-head="">
|
|
1020
|
-
<div class="help-article-head-copy">
|
|
1021
|
-
{/* In the panel, its head already names the app and leads back to the start. */}
|
|
1022
|
-
<Show when={props.surface !== "panel"}>
|
|
1023
|
-
<p class="help-article-path">
|
|
1024
|
-
<Show when={appName}>
|
|
1025
|
-
{(name) => (
|
|
1026
|
-
<>
|
|
1027
|
-
<span class="app-accent-text">{name()}</span>
|
|
1028
|
-
<i class="ti ti-chevron-right" aria-hidden="true" />
|
|
1029
|
-
</>
|
|
1030
|
-
)}
|
|
1031
|
-
</Show>
|
|
1032
|
-
<button type="button" class="help-article-path-link focus-ui" onClick={goBack}>
|
|
1033
|
-
{t().help}
|
|
1034
|
-
</button>
|
|
1035
|
-
</p>
|
|
1036
|
-
</Show>
|
|
1037
|
-
<h3 ref={articleHeading} tabindex="-1" class="help-article-title focus-ui">
|
|
1038
|
-
{topic().title}
|
|
1039
|
-
</h3>
|
|
1040
|
-
<Show
|
|
1041
|
-
when={articleParts()?.lead}
|
|
1042
|
-
fallback={
|
|
1043
|
-
// An article without a leading paragraph, or a built-in topic, shows its description; a loading article shows neither yet.
|
|
1044
|
-
<Show when={articleParts() && topic().description}>
|
|
1045
|
-
{(description) => <p class="help-article-lead">{description()}</p>}
|
|
1046
|
-
</Show>
|
|
1047
|
-
}
|
|
1048
|
-
>
|
|
1049
|
-
{(lead) => <MarkdownView trustedHtml={lead()} class="help-document help-article-lead" />}
|
|
1050
|
-
</Show>
|
|
1051
|
-
<Show when={payload()?.needs || articleParts()?.steps}>
|
|
1052
|
-
{/* One row of facts; the access level appears here and nowhere else in the reader's chrome. */}
|
|
1053
|
-
<ul class="help-article-facts" aria-label={t().articleFacts}>
|
|
1054
|
-
<Show when={payload()?.needs}>
|
|
1055
|
-
{(level) => (
|
|
1056
|
-
<li class="help-article-fact" data-fact="needs">
|
|
1057
|
-
<i class="ti ti-key" aria-hidden="true" />
|
|
1058
|
-
<span>
|
|
1059
|
-
{t().youNeed} <strong>{accessLabel(level())}</strong>
|
|
1060
|
-
</span>
|
|
1061
|
-
</li>
|
|
1062
|
-
)}
|
|
1063
|
-
</Show>
|
|
1064
|
-
<Show when={articleParts()?.steps}>
|
|
1065
|
-
{(count) => (
|
|
1066
|
-
<li class="help-article-fact" data-fact="steps">
|
|
1067
|
-
<i class="ti ti-list-numbers" aria-hidden="true" />
|
|
1068
|
-
<span>{t().steps({ count: count() })}</span>
|
|
1069
|
-
</li>
|
|
1070
|
-
)}
|
|
1071
|
-
</Show>
|
|
1072
|
-
</ul>
|
|
1073
|
-
</Show>
|
|
1074
|
-
<Show when={articleSections().length > 0 || !copyInMenu}>
|
|
1075
|
-
<div class="help-article-tools">
|
|
1076
|
-
<Show when={articleSections().length > 0}>
|
|
1077
|
-
<Dropdown.Root
|
|
1078
|
-
label={t().onThisPage}
|
|
1079
|
-
position="bottom-left"
|
|
1080
|
-
items={articleSections().map((section) => ({
|
|
1081
|
-
label: section.label,
|
|
1082
|
-
choice: "radio" as const,
|
|
1083
|
-
checked: () => activeSectionId() === section.id,
|
|
1084
|
-
action: () => openArticleSection(section),
|
|
1085
|
-
}))}
|
|
1086
|
-
>
|
|
1087
|
-
<Dropdown.Trigger label={t().onThisPage} size="sm" tooltip={false} variant="secondary">
|
|
1088
|
-
<i class="ti ti-list" aria-hidden="true" />
|
|
1089
|
-
<span>{t().onThisPage}</span>
|
|
1090
|
-
<i class="ti ti-chevron-down" aria-hidden="true" />
|
|
1091
|
-
</Dropdown.Trigger>
|
|
1092
|
-
</Dropdown.Root>
|
|
1093
|
-
</Show>
|
|
1094
|
-
<Show when={!copyInMenu}>
|
|
1095
|
-
<IconButton
|
|
1096
|
-
class="help-article-tools-copy"
|
|
1097
|
-
disabled={!payload()}
|
|
1098
|
-
label={articleClipboard.wasCopied() ? t().articleCopied : t().copyArticle}
|
|
1099
|
-
title={articleClipboard.wasCopied() ? t().copied : t().copyArticle}
|
|
1100
|
-
onClick={() => void copyArticle(topic(), payload())}
|
|
1101
|
-
>
|
|
1102
|
-
<i class={articleClipboard.wasCopied() ? "ti ti-check" : "ti ti-markdown"} />
|
|
1103
|
-
</IconButton>
|
|
1104
|
-
</Show>
|
|
1105
|
-
</div>
|
|
1106
|
-
</Show>
|
|
1107
|
-
</div>
|
|
1108
|
-
{/* The embedded readers sit in the app's own chrome and keep the flat head. */}
|
|
1109
|
-
<Show when={props.surface !== "embedded"}>
|
|
1110
|
-
<span class="help-article-icon k2b-icon-tile app-accent-text" aria-hidden="true">
|
|
1111
|
-
<i class={iconClass(topic().icon)} />
|
|
1112
|
-
</span>
|
|
1113
|
-
</Show>
|
|
1114
|
-
</header>
|
|
1115
|
-
<Show when={loading()}>
|
|
1116
|
-
<div class="min-h-[24rem]">
|
|
1117
|
-
<Placeholder state="loading" variant="panel" title={t().loading} />
|
|
1118
|
-
</div>
|
|
1119
|
-
</Show>
|
|
1120
|
-
<Show when={loadError()}>
|
|
1121
|
-
{(message) => (
|
|
1122
|
-
<NoticeCard tone="danger" title={t().loadFailed} detail={message()}>
|
|
1123
|
-
<Button size="sm" variant="secondary" onClick={() => setLoadAttempt((value) => value + 1)}>
|
|
1124
|
-
{t().tryAgain}
|
|
1125
|
-
</Button>
|
|
1126
|
-
</NoticeCard>
|
|
1127
|
-
)}
|
|
1128
|
-
</Show>
|
|
1129
|
-
<div ref={articleContent}>
|
|
1130
|
-
<Show when={articleParts()}>{(parts) => <MarkdownView trustedHtml={parts().body} class="help-document" />}</Show>
|
|
767
|
+
}
|
|
768
|
+
loading={
|
|
769
|
+
<Show when={loading()}>
|
|
770
|
+
<div class="min-h-[24rem]">
|
|
771
|
+
<Placeholder state="loading" variant="panel" title={t().loading} />
|
|
1131
772
|
</div>
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
contentRoot={() => articleContent}
|
|
1141
|
-
onActiveChange={(id) => setActiveSectionId(id ?? null)}
|
|
1142
|
-
/>
|
|
1143
|
-
<Show when={!copyInMenu}>
|
|
1144
|
-
<Button size="sm" variant="ghost" disabled={!payload()} onClick={() => void copyArticle(topic(), payload())}>
|
|
1145
|
-
<i class={articleClipboard.wasCopied() ? "ti ti-check" : "ti ti-markdown"} />
|
|
1146
|
-
{articleClipboard.wasCopied() ? t().copied : t().copyMarkdown}
|
|
773
|
+
</Show>
|
|
774
|
+
}
|
|
775
|
+
failure={
|
|
776
|
+
<Show when={loadError()}>
|
|
777
|
+
{(message) => (
|
|
778
|
+
<NoticeCard tone="danger" title={t().loadFailed} detail={message()}>
|
|
779
|
+
<Button size="sm" variant="secondary" onClick={() => setLoadAttempt((value) => value + 1)}>
|
|
780
|
+
{t().tryAgain}
|
|
1147
781
|
</Button>
|
|
1148
|
-
</
|
|
1149
|
-
|
|
1150
|
-
</
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
782
|
+
</NoticeCard>
|
|
783
|
+
)}
|
|
784
|
+
</Show>
|
|
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
|
+
/>
|
|
811
|
+
</article>
|
|
812
|
+
)}
|
|
1155
813
|
</Show>
|
|
1156
814
|
<span class="sr-only" aria-live="polite">
|
|
1157
815
|
{articleClipboard.wasCopied() ? t().articleCopiedAnnouncement : ""}
|
|
@@ -1201,22 +859,6 @@ export function LayoutHelpPage(props: LayoutHelpPageProps) {
|
|
|
1201
859
|
);
|
|
1202
860
|
}
|
|
1203
861
|
|
|
1204
|
-
/** Help inside a provider document; the server snapshot also owns the first browser render. */
|
|
1205
|
-
export function LayoutHelpPanel(props: LayoutHelpPanelProps) {
|
|
1206
|
-
const session = createSession(props.initialTopic);
|
|
1207
|
-
return (
|
|
1208
|
-
<HelpShell
|
|
1209
|
-
session={session}
|
|
1210
|
-
initialArticle={props.initialTopic ? props.initialArticle : undefined}
|
|
1211
|
-
close={() => {}}
|
|
1212
|
-
documents={props.documents}
|
|
1213
|
-
pageBase={props.pageBase}
|
|
1214
|
-
surface="panel"
|
|
1215
|
-
panel={props}
|
|
1216
|
-
/>
|
|
1217
|
-
);
|
|
1218
|
-
}
|
|
1219
|
-
|
|
1220
862
|
/** The dialog renders outside the app canvas; it takes the app's canvas colours along for the article head. */
|
|
1221
863
|
const canvasAppearance = () => {
|
|
1222
864
|
const canvas = document.querySelector(".cloud-app-canvas");
|