@k2b/cloud 0.32.0 → 0.34.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 +5 -5
- package/src/_internal/app-presentation.ts +40 -11
- package/src/_internal/define-app.ts +18 -8
- package/src/_internal/page-responses.ts +5 -1
- package/src/_internal/registry.ts +10 -1
- package/src/_internal/widget-declarations.ts +16 -3
- package/src/_internal/widget-settings.ts +93 -0
- package/src/ai/capabilities.ts +8 -5
- package/src/ai/code-source-contracts.ts +17 -2
- package/src/ai/code-source-tools.ts +1 -0
- package/src/ai/default-tools.ts +4 -4
- package/src/ai/memories.ts +48 -1
- package/src/ai/memory-learning.ts +17 -6
- package/src/ai/memory-tool.ts +1 -0
- package/src/ai/migrate.ts +8 -0
- package/src/ai/provider.ts +4 -3
- package/src/ai/resource-refs.ts +35 -3
- package/src/ai/system-prompt.ts +3 -2
- package/src/api/index.ts +2 -0
- package/src/api/mcp.ts +48 -17
- package/src/api/pwa.ts +7 -2
- package/src/api/resource-links.ts +43 -0
- package/src/api/widgets.ts +163 -82
- package/src/browser/CloudResourceSearch.tsx +6 -6
- package/src/browser/command-shortcuts.ts +2 -11
- package/src/browser/navigation-search.ts +4 -1
- package/src/browser/resource-picker.tsx +1 -0
- package/src/browser/resource-search.ts +7 -2
- package/src/browser/widgets.ts +23 -5
- package/src/capabilities/testing.ts +13 -1
- package/src/cli/capabilities.ts +61 -17
- package/src/cli/index.ts +2 -0
- package/src/cli/resource-links.ts +13 -0
- package/src/contracts/app.ts +7 -1
- package/src/contracts/capabilities.ts +25 -5
- package/src/contracts/registry.ts +3 -1
- package/src/contracts/resource-href.ts +7 -0
- package/src/contracts/widgets.ts +80 -6
- package/src/desktop/index.ts +3 -2
- package/src/desktop/solid.tsx +7 -5
- package/src/server/widget-request.ts +34 -5
- package/src/services/app-approval.ts +4 -2
- package/src/services/approvals/index.ts +40 -11
- package/src/services/approvals/list.ts +1 -0
- package/src/services/approvals/migrate.ts +10 -0
- package/src/services/outbox.ts +13 -6
- package/src/services/pdf/markdown.ts +11 -7
- package/src/services/pwa-devices.ts +68 -15
- package/src/shared/ai-platform-prompt.ts +3 -2
- package/src/shared/app-presentation.ts +49 -1
- package/src/shared/index.ts +1 -0
- package/src/shared/markdown/callout-sanitizer.ts +37 -0
- package/src/shared/markdown/index.ts +8 -2
- package/src/shared/theme.ts +41 -20
- package/src/ssr/AnnouncementBanners.tsx +30 -0
- package/src/ssr/AppLaunchpadPanel.tsx +2 -2
- package/src/ssr/CloudShell.island.tsx +91 -0
- package/src/ssr/GlobalSearchTrigger.tsx +51 -0
- package/src/ssr/Layout.tsx +58 -99
- package/src/ssr/LayoutHeader.tsx +31 -77
- package/src/ssr/LayoutHelp.browser-harness.tsx +43 -0
- package/src/ssr/LayoutHelp.tsx +319 -238
- package/src/ssr/LayoutHelpTrigger.tsx +49 -0
- package/src/ssr/LayoutPreferences.tsx +31 -0
- package/src/ssr/LayoutRail.tsx +15 -69
- package/src/ssr/MinimalLayout.tsx +2 -3
- package/src/ssr/MinimalLayoutPreferences.island.tsx +6 -15
- package/src/ssr/MobileNavigation.tsx +13 -8
- package/src/ssr/MobileProfileActions.tsx +4 -6
- package/src/ssr/PageTrailRow.tsx +14 -0
- package/src/ssr/{ProfilePreferences.island.tsx → ProfilePreferences.tsx} +8 -5
- package/src/ssr/PwaLayout.tsx +0 -2
- package/src/ssr/PwaRuntime.island.tsx +1 -1
- package/src/ssr/ShellRuntime.island.tsx +166 -0
- package/src/ssr/ThemeCycleButton.tsx +46 -0
- package/src/ssr/TimezoneCookie.island.tsx +3 -19
- package/src/ssr/WorkspaceNavigationProvider.tsx +20 -1
- package/src/ssr/announcements-dialog.tsx +66 -0
- package/src/ssr/app-appearance.ts +6 -1
- package/src/ssr/{AppLaunchpad.island.tsx → app-launchpad.tsx} +42 -62
- package/src/ssr/busy-action.ts +19 -0
- package/src/ssr/islands/index.ts +9 -6
- package/src/ssr/launchpad-surface.tsx +15 -0
- package/src/ssr/layout-help-registry.tsx +60 -0
- package/src/ssr/layout-help-url.ts +39 -0
- package/src/ssr/mobile-navigation.browser-harness.tsx +20 -0
- package/src/ssr/platform-messages.ts +0 -8
- package/src/ssr/preference-controller.ts +62 -19
- package/src/ssr/profile-actions.ts +12 -5
- package/src/ssr/profile-preferences-messages.ts +10 -4
- package/src/ssr/rail-context.ts +8 -12
- package/src/ssr/rail-navigation.ts +3 -0
- package/src/ssr/shell-data.tsx +48 -0
- package/src/ssr/timezone-cookie.ts +22 -0
- package/src/ssr/workspace-navigation.ts +25 -2
- package/src/styles/effects.css +13 -3
- package/src/styles/global.css +43 -5
- package/src/styles/input.css +0 -8
- package/src/styles/tokens.css +0 -2
- package/src/styles/utilities-navigation.css +10 -0
- package/src/workflows/language/compiler.ts +1 -1
- package/src/ssr/AppWorkspaceController.island.tsx +0 -21
- package/src/ssr/BrowserPushRegistration.island.tsx +0 -12
- package/src/ssr/GlobalAnnouncements.island.tsx +0 -117
- package/src/ssr/GlobalSearchHelpDialog.tsx +0 -104
- package/src/ssr/GlobalSearchTrigger.island.tsx +0 -75
- package/src/ssr/LayoutHelpTrigger.island.tsx +0 -61
- package/src/ssr/LayoutPreferences.island.tsx +0 -21
- package/src/ssr/PageTrailRow.island.tsx +0 -20
- package/src/ssr/PageTrailRuntime.island.tsx +0 -26
- package/src/ssr/RegisteredHelpDocuments.island.tsx +0 -3
- /package/src/ssr/{LayoutBreadcrumbs.island.tsx → LayoutBreadcrumbs.tsx} +0 -0
- /package/src/ssr/{RailApps.island.tsx → RailApps.tsx} +0 -0
package/src/ssr/LayoutHelp.tsx
CHANGED
|
@@ -1,42 +1,41 @@
|
|
|
1
|
-
import type { HelpDocumentManifest, HelpDocumentPayload
|
|
1
|
+
import type { HelpDocumentManifest, HelpDocumentPayload } from "@k2b/cloud/shared";
|
|
2
2
|
import { clipboard } from "@k2b/stdlib/solid";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
Button,
|
|
5
|
+
IconButton,
|
|
6
|
+
IconButtonLink,
|
|
7
|
+
Kbd,
|
|
8
|
+
MarkdownView,
|
|
9
|
+
NoticeCard,
|
|
10
|
+
Placeholder,
|
|
11
|
+
prompts,
|
|
12
|
+
ScrollArea,
|
|
13
|
+
TextInput,
|
|
14
|
+
useLocale,
|
|
15
|
+
} from "@k2b/ui";
|
|
4
16
|
import { createEffect, createMemo, createSignal, For, type JSX, onCleanup, onMount, Show } from "solid-js";
|
|
5
|
-
import { COMMANDS_CHANGED, contextCommandsWithShortcuts
|
|
17
|
+
import { COMMANDS_CHANGED, contextCommandsWithShortcuts } from "../browser/command-shortcuts";
|
|
6
18
|
import { appAccentStyle } from "./app-appearance";
|
|
7
|
-
import { type GlobalSearchHelpApp, openGlobalSearchHelpDialog } from "./GlobalSearchHelpDialog";
|
|
8
19
|
import { helpMessages } from "./help-messages";
|
|
9
20
|
import { formatHelpBundleMarkdown, formatHelpDocumentMarkdown } from "./layout-help-markdown";
|
|
10
21
|
import { adjacentHelpDocuments, focusHelpArticleHeading, resetHelpArticleScroll } from "./layout-help-navigation";
|
|
22
|
+
import { HELP_TOPICS_EVENT, type HelpTopic, registry } from "./layout-help-registry";
|
|
11
23
|
import { searchHelpTopics } from "./layout-help-search";
|
|
12
|
-
import { layoutHelpTopicHref } from "./layout-help-url";
|
|
24
|
+
import { helpLinkTarget, layoutHelpTopicHref } from "./layout-help-url";
|
|
25
|
+
import { readShellData, useShellData } from "./shell-data";
|
|
13
26
|
|
|
14
|
-
type HelpTopicBase = {
|
|
15
|
-
id: string;
|
|
16
|
-
title: string;
|
|
17
|
-
icon?: string;
|
|
18
|
-
description?: string;
|
|
19
|
-
order?: number;
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
export type LayoutHelpDocumentsProps = {
|
|
23
|
-
documents: readonly HelpDocumentManifest[];
|
|
24
|
-
/** Canonical standalone Help route owned by the current app. */
|
|
25
|
-
pageBase: string;
|
|
26
|
-
};
|
|
27
27
|
export type LayoutHelpPageProps = {
|
|
28
28
|
documents: readonly HelpDocumentManifest[];
|
|
29
29
|
initialTopic?: string;
|
|
30
30
|
includeShortcuts?: boolean;
|
|
31
31
|
accent?: string;
|
|
32
|
-
/** Render inside an app-owned pane instead of occupying a
|
|
32
|
+
/** Render inside an app-owned pane instead of occupying a full page. */
|
|
33
33
|
embedded?: boolean;
|
|
34
|
-
/** Canonical
|
|
34
|
+
/** Canonical full-page Help route owned by the current app. */
|
|
35
35
|
pageBase?: string;
|
|
36
36
|
};
|
|
37
37
|
|
|
38
|
-
type
|
|
39
|
-
type HelpView = "hub" | "search" | "article";
|
|
38
|
+
type HelpView = "hub" | "article";
|
|
40
39
|
type HelpSession = {
|
|
41
40
|
view: HelpView;
|
|
42
41
|
query: string;
|
|
@@ -51,23 +50,8 @@ type HelpSection = {
|
|
|
51
50
|
title: string;
|
|
52
51
|
};
|
|
53
52
|
|
|
54
|
-
const HELP_TOPICS_EVENT = "cloud:layout-help-topics";
|
|
55
53
|
const LAST_TOPIC_KEY = "cloud.layoutHelp.activeTopic";
|
|
56
54
|
|
|
57
|
-
declare global {
|
|
58
|
-
interface Window {
|
|
59
|
-
__cloudLayoutHelpTopics?: Map<string, HelpTopic>;
|
|
60
|
-
__cloudLayoutHelpPageBase?: string;
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
const registry = () => {
|
|
65
|
-
if (typeof window === "undefined") return null;
|
|
66
|
-
window.__cloudLayoutHelpTopics ??= new Map<string, HelpTopic>();
|
|
67
|
-
return window.__cloudLayoutHelpTopics;
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
const emitTopicsChanged = () => window.dispatchEvent(new Event(HELP_TOPICS_EVENT));
|
|
71
55
|
const iconClass = (icon?: string) => (icon?.startsWith("ti ") ? icon : `ti ${icon ?? "ti-circle"}`);
|
|
72
56
|
const legacyTopicContent = (topic: HelpTopic) => (topic.kind === "content" ? topic.children : null);
|
|
73
57
|
const sortedTopics = () =>
|
|
@@ -80,34 +64,7 @@ const mergeTopics = (registered: readonly HelpTopic[], documents: readonly HelpD
|
|
|
80
64
|
return [...topics.values()].sort((a, b) => (a.order ?? 100) - (b.order ?? 100) || a.title.localeCompare(b.title));
|
|
81
65
|
};
|
|
82
66
|
|
|
83
|
-
const
|
|
84
|
-
const topics = registry();
|
|
85
|
-
if (!topics) return () => {};
|
|
86
|
-
topics.set(topic.id, topic);
|
|
87
|
-
emitTopicsChanged();
|
|
88
|
-
return () => {
|
|
89
|
-
if (topics.get(topic.id) !== topic) return;
|
|
90
|
-
topics.delete(topic.id);
|
|
91
|
-
emitTopicsChanged();
|
|
92
|
-
};
|
|
93
|
-
};
|
|
94
|
-
|
|
95
|
-
/** Register an app's server-prepared Markdown manifest with the shared help UI. */
|
|
96
|
-
export function LayoutHelpDocuments(props: LayoutHelpDocumentsProps) {
|
|
97
|
-
onMount(() => {
|
|
98
|
-
// Cleanup runs while this unmounts, when a parent `<Show>` may already have dropped the props' source.
|
|
99
|
-
const pageBase = props.pageBase;
|
|
100
|
-
window.__cloudLayoutHelpPageBase = pageBase;
|
|
101
|
-
const disposers = props.documents.map((document) => registerTopic({ ...document, kind: "document" }));
|
|
102
|
-
onCleanup(() => {
|
|
103
|
-
disposers.forEach((dispose) => dispose());
|
|
104
|
-
if (window.__cloudLayoutHelpPageBase === pageBase) delete window.__cloudLayoutHelpPageBase;
|
|
105
|
-
});
|
|
106
|
-
});
|
|
107
|
-
return null;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
const Shortcuts = (props: { openSearchHelp: () => void }) => {
|
|
67
|
+
const Shortcuts = (props: { focusSearch: () => void }) => {
|
|
111
68
|
const locale = useLocale();
|
|
112
69
|
const t = () => helpMessages.resolve([locale()]).t;
|
|
113
70
|
const [commands, setCommands] = createSignal(contextCommandsWithShortcuts());
|
|
@@ -120,7 +77,7 @@ const Shortcuts = (props: { openSearchHelp: () => void }) => {
|
|
|
120
77
|
return (
|
|
121
78
|
<div class="flex flex-col gap-3">
|
|
122
79
|
<p class="text-sm leading-relaxed text-dimmed">{t().shortcutsIntro}</p>
|
|
123
|
-
<Button size="sm" variant="secondary" class="self-start" onClick={props.
|
|
80
|
+
<Button size="sm" variant="secondary" class="self-start" onClick={props.focusSearch}>
|
|
124
81
|
<i class="ti ti-search" /> {t().searchHelp}
|
|
125
82
|
</Button>
|
|
126
83
|
<div class="flex flex-col gap-2">
|
|
@@ -133,9 +90,7 @@ const Shortcuts = (props: { openSearchHelp: () => void }) => {
|
|
|
133
90
|
<p class="mt-0.5 text-xs text-dimmed">{entry.description}</p>
|
|
134
91
|
</Show>
|
|
135
92
|
</div>
|
|
136
|
-
<
|
|
137
|
-
{shortcutLabel(entry.shortcut!)}
|
|
138
|
-
</kbd>
|
|
93
|
+
<Kbd keys={entry.shortcut!} />
|
|
139
94
|
</div>
|
|
140
95
|
)}
|
|
141
96
|
</For>
|
|
@@ -148,7 +103,6 @@ const HelpShell = (props: {
|
|
|
148
103
|
session: HelpSession;
|
|
149
104
|
close: () => void;
|
|
150
105
|
pageBase?: string;
|
|
151
|
-
searchHelpApps: GlobalSearchHelpApp[];
|
|
152
106
|
documents?: readonly HelpDocumentManifest[];
|
|
153
107
|
includeShortcuts?: boolean;
|
|
154
108
|
accent?: string;
|
|
@@ -157,6 +111,8 @@ const HelpShell = (props: {
|
|
|
157
111
|
}) => {
|
|
158
112
|
const locale = useLocale();
|
|
159
113
|
const t = () => helpMessages.resolve([locale()]).t;
|
|
114
|
+
// Links to this app's Help, by its own route or Core's `/help/apps/<id>`, open in place.
|
|
115
|
+
const appId = useShellData()?.appId;
|
|
160
116
|
const [externalTopics, setExternalTopics] = createSignal(mergeTopics(sortedTopics(), props.documents));
|
|
161
117
|
const [view, setView] = createSignal<HelpView>(props.session.view);
|
|
162
118
|
const [query, setQuery] = createSignal(props.session.query);
|
|
@@ -177,20 +133,45 @@ const HelpShell = (props: {
|
|
|
177
133
|
let scrollArea: HTMLDivElement | undefined;
|
|
178
134
|
let articleContent: HTMLDivElement | undefined;
|
|
179
135
|
let articleHeading: HTMLHeadingElement | undefined;
|
|
136
|
+
let searchInput: HTMLInputElement | undefined;
|
|
137
|
+
let resultsRegion: HTMLDivElement | undefined;
|
|
138
|
+
const [resultsMinHeight, setResultsMinHeight] = createSignal<string>();
|
|
180
139
|
let pendingArticleFocus = false;
|
|
140
|
+
let pendingNavigation = false;
|
|
141
|
+
/** Where the page's own address points in this Help; a query string, such as a campaign tag, does not matter here. */
|
|
142
|
+
const currentHelpTarget = () =>
|
|
143
|
+
helpLinkTarget(`${location.pathname}${location.hash}`, location.href, { appId, pageBase: props.pageBase });
|
|
144
|
+
let pendingHash = props.syncPageUrl && typeof location !== "undefined" ? (currentHelpTarget()?.hash ?? "") : "";
|
|
145
|
+
/**
|
|
146
|
+
* The address Help shows now. The phone menu and dialogs with a history entry put a second entry on the same address
|
|
147
|
+
* and take it away again; that is not Help navigation and must not move the article or the search.
|
|
148
|
+
*/
|
|
149
|
+
const pageAddress = () => `${location.pathname}${location.hash}`;
|
|
150
|
+
let shownAddress = props.syncPageUrl && typeof location !== "undefined" ? pageAddress() : "";
|
|
181
151
|
let requestVersion = 0;
|
|
182
152
|
let searchVersion = 0;
|
|
183
153
|
let copyAllController: AbortController | undefined;
|
|
184
154
|
|
|
185
|
-
const
|
|
155
|
+
const articleScroller = () => {
|
|
156
|
+
if (props.surface !== "page") return scrollArea;
|
|
157
|
+
for (let element = root?.parentElement; element; element = element.parentElement) {
|
|
158
|
+
if (/(auto|scroll)/.test(getComputedStyle(element).overflowY)) return element;
|
|
159
|
+
}
|
|
160
|
+
return document.scrollingElement;
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
const restoreArticleScroll = () => {
|
|
164
|
+
if (props.surface !== "modal" || pendingNavigation || pendingHash) return;
|
|
186
165
|
requestAnimationFrame(() => {
|
|
187
166
|
if (scrollArea && view() === "article") scrollArea.scrollTop = props.session.articleScrollTop;
|
|
188
167
|
});
|
|
168
|
+
};
|
|
189
169
|
|
|
190
|
-
const syncActiveSection = () => {
|
|
170
|
+
const syncActiveSection = (scroller = articleScroller()) => {
|
|
191
171
|
const sections = articleSections();
|
|
192
|
-
if (!
|
|
193
|
-
const
|
|
172
|
+
if (!scroller || sections.length === 0) return;
|
|
173
|
+
const top = scroller === document.scrollingElement ? 0 : scroller.getBoundingClientRect().top;
|
|
174
|
+
const readingLine = top + Math.min(160, scroller.clientHeight * 0.22);
|
|
194
175
|
let active = sections[0];
|
|
195
176
|
for (const section of sections) {
|
|
196
177
|
if (section.element.getBoundingClientRect().top <= readingLine) active = section;
|
|
@@ -199,6 +180,16 @@ const HelpShell = (props: {
|
|
|
199
180
|
setActiveSectionId(active?.id ?? null);
|
|
200
181
|
};
|
|
201
182
|
|
|
183
|
+
const scrollToHash = (hash: string) => {
|
|
184
|
+
const heading = Array.from(
|
|
185
|
+
articleContent?.querySelectorAll<HTMLHeadingElement>("h1[id], h2[id], h3[id], h4[id], h5[id], h6[id]") ?? [],
|
|
186
|
+
).find((element) => element.id === hash);
|
|
187
|
+
if (!heading) return false;
|
|
188
|
+
setActiveSectionId(hash);
|
|
189
|
+
heading.scrollIntoView({ block: "start", behavior: window.matchMedia("(prefers-reduced-motion: reduce)").matches ? "auto" : "smooth" });
|
|
190
|
+
return true;
|
|
191
|
+
};
|
|
192
|
+
|
|
202
193
|
const collectArticleSections = () => {
|
|
203
194
|
const sections = Array.from(articleContent?.querySelectorAll<HTMLHeadingElement>("h2[id]") ?? []).map((element) => ({
|
|
204
195
|
element,
|
|
@@ -208,15 +199,25 @@ const HelpShell = (props: {
|
|
|
208
199
|
}));
|
|
209
200
|
setArticleSections(sections);
|
|
210
201
|
setActiveSectionId(sections[0]?.id ?? null);
|
|
211
|
-
|
|
202
|
+
const openedSection = pendingHash ? scrollToHash(pendingHash) : false;
|
|
203
|
+
pendingHash = "";
|
|
204
|
+
pendingNavigation = false;
|
|
205
|
+
if (!openedSection) syncActiveSection();
|
|
212
206
|
};
|
|
213
207
|
|
|
214
208
|
const openArticleSection = (section: HelpSection) => {
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
209
|
+
scrollToHash(section.id);
|
|
210
|
+
if (props.syncPageUrl) {
|
|
211
|
+
const url = new URL(location.href);
|
|
212
|
+
url.hash = encodeURIComponent(section.id);
|
|
213
|
+
history.replaceState(history.state, "", url);
|
|
214
|
+
shownAddress = pageAddress();
|
|
215
|
+
}
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
const focusSearch = () => {
|
|
219
|
+
showHub();
|
|
220
|
+
requestAnimationFrame(() => searchInput?.focus({ preventScroll: true }));
|
|
220
221
|
};
|
|
221
222
|
|
|
222
223
|
const shortcutsTopic = createMemo<HelpTopic>(() => ({
|
|
@@ -226,7 +227,7 @@ const HelpShell = (props: {
|
|
|
226
227
|
description: t().shortcutsDescription,
|
|
227
228
|
order: 0,
|
|
228
229
|
kind: "content",
|
|
229
|
-
children: <Shortcuts
|
|
230
|
+
children: <Shortcuts focusSearch={focusSearch} />,
|
|
230
231
|
}));
|
|
231
232
|
const topics = createMemo(() => (props.includeShortcuts === false ? externalTopics() : [shortcutsTopic(), ...externalTopics()]));
|
|
232
233
|
const documentTopics = createMemo(() =>
|
|
@@ -265,8 +266,10 @@ const HelpShell = (props: {
|
|
|
265
266
|
headers: { Accept: "application/json" },
|
|
266
267
|
});
|
|
267
268
|
if (!response.ok) throw new Error(t().searchFailed);
|
|
268
|
-
const payload =
|
|
269
|
-
return
|
|
269
|
+
const payload: unknown = await response.json();
|
|
270
|
+
return payload && typeof payload === "object" && "ids" in payload && Array.isArray(payload.ids)
|
|
271
|
+
? payload.ids.filter((id): id is string => typeof id === "string")
|
|
272
|
+
: [];
|
|
270
273
|
}),
|
|
271
274
|
)
|
|
272
275
|
.then((groups) => {
|
|
@@ -295,7 +298,13 @@ const HelpShell = (props: {
|
|
|
295
298
|
setActiveSectionId(null);
|
|
296
299
|
return;
|
|
297
300
|
}
|
|
298
|
-
const frame = requestAnimationFrame(
|
|
301
|
+
const frame = requestAnimationFrame(() => {
|
|
302
|
+
if (pendingNavigation && !pendingHash) {
|
|
303
|
+
const scroller = articleScroller();
|
|
304
|
+
if (scroller) scroller.scrollTop = 0;
|
|
305
|
+
}
|
|
306
|
+
collectArticleSections();
|
|
307
|
+
});
|
|
299
308
|
onCleanup(() => cancelAnimationFrame(frame));
|
|
300
309
|
});
|
|
301
310
|
|
|
@@ -327,6 +336,35 @@ const HelpShell = (props: {
|
|
|
327
336
|
update();
|
|
328
337
|
onCleanup(() => window.removeEventListener(HELP_TOPICS_EVENT, update));
|
|
329
338
|
restoreArticleScroll();
|
|
339
|
+
let scrollFrame: number | undefined;
|
|
340
|
+
const onScroll = (event: Event) => {
|
|
341
|
+
const scroller = event.target === document ? document.scrollingElement : event.target;
|
|
342
|
+
if (!(scroller instanceof Element) || !articleContent || !scroller.contains(articleContent) || view() !== "article") return;
|
|
343
|
+
if (props.surface === "modal" && scroller === scrollArea) props.session.articleScrollTop = scroller.scrollTop;
|
|
344
|
+
if (scrollFrame !== undefined) return;
|
|
345
|
+
scrollFrame = requestAnimationFrame(() => {
|
|
346
|
+
scrollFrame = undefined;
|
|
347
|
+
syncActiveSection(scroller);
|
|
348
|
+
});
|
|
349
|
+
};
|
|
350
|
+
document.addEventListener("scroll", onScroll, { capture: true, passive: true });
|
|
351
|
+
const onPopState = () => {
|
|
352
|
+
const address = pageAddress();
|
|
353
|
+
if (address === shownAddress) return;
|
|
354
|
+
const target = currentHelpTarget();
|
|
355
|
+
if (target?.kind !== "topic") return;
|
|
356
|
+
shownAddress = address;
|
|
357
|
+
const id = target.topicId;
|
|
358
|
+
if (id !== null && topics().some((topic) => topic.id === id)) openTopic(id, target.hash, false);
|
|
359
|
+
// The hub keeps its search, as the reader's own Back button does. An unknown topic shows the hub, as the server does.
|
|
360
|
+
else setView("hub");
|
|
361
|
+
};
|
|
362
|
+
if (props.syncPageUrl) window.addEventListener("popstate", onPopState);
|
|
363
|
+
onCleanup(() => {
|
|
364
|
+
document.removeEventListener("scroll", onScroll, true);
|
|
365
|
+
window.removeEventListener("popstate", onPopState);
|
|
366
|
+
if (scrollFrame !== undefined) cancelAnimationFrame(scrollFrame);
|
|
367
|
+
});
|
|
330
368
|
});
|
|
331
369
|
onCleanup(() => copyAllController?.abort());
|
|
332
370
|
|
|
@@ -337,18 +375,30 @@ const HelpShell = (props: {
|
|
|
337
375
|
|
|
338
376
|
const response = await fetch(topic.url, { signal, headers: { Accept: "application/json" } });
|
|
339
377
|
if (!response.ok) throw new Error(t().requestFailed);
|
|
340
|
-
const value =
|
|
378
|
+
const value: unknown = await response.json();
|
|
341
379
|
if (
|
|
380
|
+
!value ||
|
|
381
|
+
typeof value !== "object" ||
|
|
382
|
+
!("id" in value) ||
|
|
342
383
|
value.id !== topic.id ||
|
|
343
|
-
(
|
|
344
|
-
(
|
|
384
|
+
!("html" in value) ||
|
|
385
|
+
!("markdown" in value) ||
|
|
386
|
+
!("title" in value) ||
|
|
387
|
+
("locale" in value && value.locale !== undefined && typeof value.locale !== "string") ||
|
|
388
|
+
(topic.locale !== undefined && "locale" in value && value.locale !== undefined && value.locale !== topic.locale) ||
|
|
345
389
|
typeof value.html !== "string" ||
|
|
346
390
|
typeof value.markdown !== "string" ||
|
|
347
391
|
typeof value.title !== "string"
|
|
348
392
|
) {
|
|
349
393
|
throw new Error(t().invalidDocument);
|
|
350
394
|
}
|
|
351
|
-
const document: HelpDocumentPayload = {
|
|
395
|
+
const document: HelpDocumentPayload = {
|
|
396
|
+
id: topic.id,
|
|
397
|
+
title: value.title,
|
|
398
|
+
html: value.html,
|
|
399
|
+
markdown: value.markdown,
|
|
400
|
+
locale: "locale" in value && typeof value.locale === "string" ? value.locale : (topic.locale ?? "en"),
|
|
401
|
+
};
|
|
352
402
|
props.session.articleCache.set(cacheKey, document);
|
|
353
403
|
return document;
|
|
354
404
|
};
|
|
@@ -424,44 +474,79 @@ const HelpShell = (props: {
|
|
|
424
474
|
}
|
|
425
475
|
};
|
|
426
476
|
|
|
427
|
-
const
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
setView("article");
|
|
432
|
-
if (props.syncPageUrl) {
|
|
433
|
-
if (props.pageBase) history.replaceState(history.state, "", layoutHelpTopicHref(props.pageBase, id));
|
|
477
|
+
const pushHelpUrl = (id: string | null, hash = "") => {
|
|
478
|
+
if (props.syncPageUrl && props.pageBase) {
|
|
479
|
+
history.pushState(history.state, "", layoutHelpTopicHref(props.pageBase, id) + (hash ? `#${encodeURIComponent(hash)}` : ""));
|
|
480
|
+
shownAddress = pageAddress();
|
|
434
481
|
}
|
|
482
|
+
};
|
|
483
|
+
const openTopic = (id: string, hash = "", push = true) => {
|
|
484
|
+
if (push) pushHelpUrl(id, hash);
|
|
435
485
|
try {
|
|
436
486
|
localStorage.setItem(LAST_TOPIC_KEY, id);
|
|
437
487
|
} catch {
|
|
438
488
|
/* Help does not depend on storage. */
|
|
439
489
|
}
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
if (props.pageBase) history.replaceState(history.state, "", layoutHelpTopicHref(props.pageBase, null));
|
|
490
|
+
// The open article does not load again: go to its section, or to its top.
|
|
491
|
+
if (view() === "article" && activeId() === id && payload()) {
|
|
492
|
+
if (!hash || !scrollToHash(hash)) resetHelpArticleScroll(props.session, articleScroller() ?? undefined);
|
|
493
|
+
return;
|
|
445
494
|
}
|
|
495
|
+
resetHelpArticleScroll(props.session, articleScroller() ?? undefined);
|
|
496
|
+
pendingArticleFocus = true;
|
|
497
|
+
pendingNavigation = true;
|
|
498
|
+
pendingHash = hash;
|
|
499
|
+
setActiveId(id);
|
|
500
|
+
setView("article");
|
|
446
501
|
};
|
|
447
|
-
const showHub = () => {
|
|
502
|
+
const showHub = (push = true) => {
|
|
448
503
|
setQuery("");
|
|
504
|
+
setResultsMinHeight(undefined);
|
|
449
505
|
setRemoteIds(null);
|
|
450
506
|
setView("hub");
|
|
507
|
+
if (push) pushHelpUrl(null);
|
|
451
508
|
};
|
|
509
|
+
const goBack = () => {
|
|
510
|
+
setView("hub");
|
|
511
|
+
pushHelpUrl(null);
|
|
512
|
+
};
|
|
513
|
+
|
|
514
|
+
const followHelpLink = (event: MouseEvent) => {
|
|
515
|
+
if (event.defaultPrevented || event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;
|
|
516
|
+
if (!(event.target instanceof Element)) return;
|
|
517
|
+
const anchor = event.target.closest("a");
|
|
518
|
+
if (!anchor || !articleContent?.contains(anchor) || (anchor.target && anchor.target !== "_self") || anchor.hasAttribute("download"))
|
|
519
|
+
return;
|
|
520
|
+
const target = helpLinkTarget(anchor.getAttribute("href") ?? "", location.href, { appId, pageBase: props.pageBase });
|
|
521
|
+
if (!target) return;
|
|
522
|
+
if (target.kind === "section") {
|
|
523
|
+
if (!scrollToHash(target.hash)) return;
|
|
524
|
+
event.preventDefault();
|
|
525
|
+
if (props.syncPageUrl) {
|
|
526
|
+
const url = new URL(location.href);
|
|
527
|
+
url.hash = encodeURIComponent(target.hash);
|
|
528
|
+
history.replaceState(history.state, "", url);
|
|
529
|
+
shownAddress = pageAddress();
|
|
530
|
+
}
|
|
531
|
+
} else if (target.topicId === null) {
|
|
532
|
+
event.preventDefault();
|
|
533
|
+
showHub();
|
|
534
|
+
} else if (topics().some((topic) => topic.id === target.topicId)) {
|
|
535
|
+
event.preventDefault();
|
|
536
|
+
openTopic(target.topicId, target.hash);
|
|
537
|
+
}
|
|
538
|
+
};
|
|
539
|
+
|
|
452
540
|
const modalTitle = createMemo(() => {
|
|
453
541
|
if (view() === "article") return activeTopic()?.title ?? t().help;
|
|
454
|
-
if (view() === "search") return t().searchHelp;
|
|
455
542
|
return t().help;
|
|
456
543
|
});
|
|
457
544
|
const modalDescription = createMemo(() => {
|
|
458
545
|
if (view() === "article") return activeTopic()?.description ?? t().description;
|
|
459
|
-
if (view() === "search") return t().searchDescription;
|
|
460
546
|
return t().description;
|
|
461
547
|
});
|
|
462
548
|
const modalIcon = createMemo(() => {
|
|
463
549
|
if (view() === "article") return iconClass(activeTopic()?.icon);
|
|
464
|
-
if (view() === "search") return "ti ti-search";
|
|
465
550
|
return "ti ti-help";
|
|
466
551
|
});
|
|
467
552
|
|
|
@@ -527,14 +612,23 @@ const HelpShell = (props: {
|
|
|
527
612
|
</Show>
|
|
528
613
|
);
|
|
529
614
|
|
|
615
|
+
const ReaderContent = (content: { children: JSX.Element }) =>
|
|
616
|
+
props.surface === "page" ? (
|
|
617
|
+
<div class="p-4 sm:p-5">{content.children}</div>
|
|
618
|
+
) : (
|
|
619
|
+
<ScrollArea ref={scrollArea} class="min-h-0 flex-1 p-4 sm:p-5">
|
|
620
|
+
{content.children}
|
|
621
|
+
</ScrollArea>
|
|
622
|
+
);
|
|
623
|
+
|
|
530
624
|
return (
|
|
531
625
|
<div
|
|
532
626
|
ref={root}
|
|
533
|
-
class={`app-accent-scope flex min-h-0 flex-col bg-[var(--ui-surface-raised)] ${
|
|
627
|
+
class={`app-accent-scope flex min-h-0 flex-col ${props.surface === "page" ? "" : "bg-[var(--ui-surface-raised)]"} ${
|
|
534
628
|
props.surface === "modal"
|
|
535
629
|
? "h-[min(48rem,var(--ui-dialog-available-height))] overflow-hidden panel-dialog-shell [box-shadow:var(--ui-shadow-float)]"
|
|
536
630
|
: props.surface === "page"
|
|
537
|
-
? "
|
|
631
|
+
? ""
|
|
538
632
|
: "h-full"
|
|
539
633
|
}`}
|
|
540
634
|
style={appAccentStyle(props.accent)}
|
|
@@ -542,12 +636,7 @@ const HelpShell = (props: {
|
|
|
542
636
|
<Show when={props.surface === "modal"}>
|
|
543
637
|
<header class="flex min-h-16 shrink-0 items-center gap-2 px-5 py-3">
|
|
544
638
|
<Show when={view() !== "hub"}>
|
|
545
|
-
<IconButton
|
|
546
|
-
class="shrink-0"
|
|
547
|
-
label={view() === "article" ? t().backToTopics : t().backToHelp}
|
|
548
|
-
title={t().back}
|
|
549
|
-
onClick={() => (view() === "article" ? goBack() : showHub())}
|
|
550
|
-
>
|
|
639
|
+
<IconButton class="shrink-0" label={t().backToTopics} title={t().back} onClick={goBack}>
|
|
551
640
|
<i class="ti ti-arrow-left" />
|
|
552
641
|
</IconButton>
|
|
553
642
|
</Show>
|
|
@@ -620,16 +709,7 @@ const HelpShell = (props: {
|
|
|
620
709
|
</header>
|
|
621
710
|
</Show>
|
|
622
711
|
|
|
623
|
-
<
|
|
624
|
-
ref={scrollArea}
|
|
625
|
-
class="min-h-0 flex-1 p-4 sm:p-5"
|
|
626
|
-
onScroll={(event) => {
|
|
627
|
-
if (view() === "article") {
|
|
628
|
-
props.session.articleScrollTop = event.currentTarget.scrollTop;
|
|
629
|
-
syncActiveSection();
|
|
630
|
-
}
|
|
631
|
-
}}
|
|
632
|
-
>
|
|
712
|
+
<ReaderContent>
|
|
633
713
|
<Show when={view() === "hub"}>
|
|
634
714
|
<div class="mx-auto flex max-w-3xl flex-col gap-6">
|
|
635
715
|
<div class="flex flex-col items-start justify-between gap-3 sm:flex-row">
|
|
@@ -651,70 +731,70 @@ const HelpShell = (props: {
|
|
|
651
731
|
</p>
|
|
652
732
|
)}
|
|
653
733
|
</Show>
|
|
654
|
-
<
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
<
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
734
|
+
<TextInput
|
|
735
|
+
ref={searchInput}
|
|
736
|
+
type="search"
|
|
737
|
+
icon="ti ti-search"
|
|
738
|
+
aria-label={t().searchHelp}
|
|
739
|
+
data-help-search=""
|
|
740
|
+
value={query()}
|
|
741
|
+
placeholder={t().searchPlaceholder}
|
|
742
|
+
onValueChange={(value) => {
|
|
743
|
+
// Results replace the start lists below the field; the area keeps its height so nothing moves.
|
|
744
|
+
if (!query().trim() && value.trim()) setResultsMinHeight(`${resultsRegion?.offsetHeight ?? 0}px`);
|
|
745
|
+
if (!value.trim()) setResultsMinHeight(undefined);
|
|
746
|
+
setQuery(value);
|
|
747
|
+
}}
|
|
748
|
+
/>
|
|
749
|
+
<div ref={resultsRegion} data-help-results="" class="flex flex-col gap-4" style={{ "min-height": resultsMinHeight() }}>
|
|
750
|
+
<Show
|
|
751
|
+
when={normalizedQuery()}
|
|
752
|
+
fallback={
|
|
753
|
+
<>
|
|
754
|
+
<section aria-labelledby="help-start-title">
|
|
755
|
+
<h4 id="help-start-title" class="mb-2 text-xs font-semibold uppercase tracking-wide text-dimmed">
|
|
756
|
+
{t().startHere}
|
|
757
|
+
</h4>
|
|
758
|
+
<TopicList items={topics().slice(0, 5)} />
|
|
759
|
+
</section>
|
|
760
|
+
<Show when={topics().length > 5}>
|
|
761
|
+
<section aria-labelledby="help-all-title">
|
|
762
|
+
<h4 id="help-all-title" class="mb-2 text-xs font-semibold uppercase tracking-wide text-dimmed">
|
|
763
|
+
{t().allTopics}
|
|
764
|
+
</h4>
|
|
765
|
+
<TopicList items={topics().slice(5)} />
|
|
766
|
+
</section>
|
|
767
|
+
</Show>
|
|
768
|
+
</>
|
|
769
|
+
}
|
|
770
|
+
>
|
|
771
|
+
{/* The count takes the place of the "Start here" title, so the first row stays where it was. */}
|
|
772
|
+
<section>
|
|
773
|
+
<p class="mb-2 flex items-center gap-1.5 text-xs font-semibold uppercase tracking-wide text-dimmed" aria-live="polite">
|
|
774
|
+
{t().results({ count: results().length })}
|
|
775
|
+
<Show when={searching()}>
|
|
776
|
+
<i class="ti ti-loader-2 animate-spin" aria-hidden="true" />
|
|
777
|
+
</Show>
|
|
778
|
+
</p>
|
|
779
|
+
<Show
|
|
780
|
+
when={results().length > 0}
|
|
781
|
+
fallback={
|
|
782
|
+
<Show when={!searching()}>
|
|
783
|
+
<Placeholder icon="ti ti-search-off" title={t().noResults} />
|
|
784
|
+
</Show>
|
|
785
|
+
}
|
|
786
|
+
>
|
|
787
|
+
<TopicList items={results()} />
|
|
788
|
+
</Show>
|
|
789
|
+
</section>
|
|
690
790
|
</Show>
|
|
691
|
-
<label class="field flex h-11 flex-1 items-center gap-2 px-3">
|
|
692
|
-
<i class="ti ti-search text-dimmed" />
|
|
693
|
-
<input
|
|
694
|
-
autofocus
|
|
695
|
-
class="min-w-0 flex-1 bg-transparent text-sm outline-none"
|
|
696
|
-
value={query()}
|
|
697
|
-
placeholder={t().searchPlaceholder}
|
|
698
|
-
onInput={(event) => setQuery(event.currentTarget.value)}
|
|
699
|
-
/>
|
|
700
|
-
</label>
|
|
701
791
|
</div>
|
|
702
|
-
<p class="flex items-center gap-1.5 text-xs text-dimmed" aria-live="polite">
|
|
703
|
-
<Show when={searching()}>
|
|
704
|
-
<i class="ti ti-loader-2 animate-spin" aria-hidden="true" />
|
|
705
|
-
</Show>
|
|
706
|
-
{t().results({ count: results().length })}
|
|
707
|
-
</p>
|
|
708
|
-
<TopicList items={results()} />
|
|
709
|
-
<Show when={results().length === 0 && !searching()}>
|
|
710
|
-
<Placeholder icon="ti ti-search-off" title={t().noResults} />
|
|
711
|
-
</Show>
|
|
712
792
|
</div>
|
|
713
793
|
</Show>
|
|
714
794
|
|
|
715
795
|
<Show when={view() === "article" && activeTopic()}>
|
|
716
796
|
{(topic) => (
|
|
717
|
-
<article class="mx-auto flex min-h-full w-full max-w-7xl flex-col">
|
|
797
|
+
<article class="help-article mx-auto flex min-h-full w-full max-w-7xl flex-col">
|
|
718
798
|
<Show when={props.surface !== "modal"}>
|
|
719
799
|
<button
|
|
720
800
|
type="button"
|
|
@@ -750,54 +830,63 @@ const HelpShell = (props: {
|
|
|
750
830
|
</Show>
|
|
751
831
|
<Show when={topic().kind === "content"}>{legacyTopicContent(topic())}</Show>
|
|
752
832
|
<Show when={topic().kind === "document"}>
|
|
753
|
-
<
|
|
754
|
-
<
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
</button>
|
|
786
|
-
</li>
|
|
787
|
-
)}
|
|
788
|
-
</For>
|
|
789
|
-
</ol>
|
|
790
|
-
</nav>
|
|
791
|
-
</Show>
|
|
792
|
-
<div class="help-article-copy">
|
|
793
|
-
<div ref={articleContent}>
|
|
794
|
-
<MarkdownView trustedHtml={document().html} class="help-document" />
|
|
795
|
-
</div>
|
|
796
|
-
<TopicNavigation />
|
|
833
|
+
<div class="help-article-layout">
|
|
834
|
+
<nav
|
|
835
|
+
class="help-article-toc"
|
|
836
|
+
aria-label={t().onThisPage}
|
|
837
|
+
aria-hidden={articleSections().length === 0 ? "true" : undefined}
|
|
838
|
+
data-empty={articleSections().length === 0 ? "" : undefined}
|
|
839
|
+
>
|
|
840
|
+
<p class="help-article-toc-label">{t().onThisPage}</p>
|
|
841
|
+
<ol>
|
|
842
|
+
<For each={articleSections()}>
|
|
843
|
+
{(section) => (
|
|
844
|
+
<li>
|
|
845
|
+
<button
|
|
846
|
+
type="button"
|
|
847
|
+
aria-current={activeSectionId() === section.id ? "location" : undefined}
|
|
848
|
+
title={section.title}
|
|
849
|
+
onClick={() => openArticleSection(section)}
|
|
850
|
+
>
|
|
851
|
+
<span class="help-article-toc-icon" aria-hidden="true">
|
|
852
|
+
<i class={section.icon} />
|
|
853
|
+
</span>
|
|
854
|
+
<span class="help-article-toc-title">{section.title}</span>
|
|
855
|
+
</button>
|
|
856
|
+
</li>
|
|
857
|
+
)}
|
|
858
|
+
</For>
|
|
859
|
+
</ol>
|
|
860
|
+
</nav>
|
|
861
|
+
<div class="help-article-copy">
|
|
862
|
+
<Show when={loading()}>
|
|
863
|
+
<div class="min-h-[24rem]">
|
|
864
|
+
<Placeholder state="loading" variant="panel" title={t().loading} />
|
|
797
865
|
</div>
|
|
866
|
+
</Show>
|
|
867
|
+
<Show when={loadError()}>
|
|
868
|
+
{(message) => (
|
|
869
|
+
<NoticeCard tone="danger" title={t().loadFailed} detail={message()}>
|
|
870
|
+
<Button size="sm" variant="secondary" onClick={() => setLoadAttempt((value) => value + 1)}>
|
|
871
|
+
{t().tryAgain}
|
|
872
|
+
</Button>
|
|
873
|
+
</NoticeCard>
|
|
874
|
+
)}
|
|
875
|
+
</Show>
|
|
876
|
+
<div
|
|
877
|
+
ref={(element) => {
|
|
878
|
+
articleContent = element;
|
|
879
|
+
element.addEventListener("click", followHelpLink);
|
|
880
|
+
onCleanup(() => element.removeEventListener("click", followHelpLink));
|
|
881
|
+
}}
|
|
882
|
+
>
|
|
883
|
+
<Show when={payload()}>{(document) => <MarkdownView trustedHtml={document().html} class="help-document" />}</Show>
|
|
798
884
|
</div>
|
|
799
|
-
|
|
800
|
-
|
|
885
|
+
<Show when={payload()}>
|
|
886
|
+
<TopicNavigation />
|
|
887
|
+
</Show>
|
|
888
|
+
</div>
|
|
889
|
+
</div>
|
|
801
890
|
</Show>
|
|
802
891
|
</article>
|
|
803
892
|
)}
|
|
@@ -805,7 +894,7 @@ const HelpShell = (props: {
|
|
|
805
894
|
<span class="sr-only" aria-live="polite">
|
|
806
895
|
{articleClipboard.wasCopied() ? t().articleCopiedAnnouncement : allHelpClipboard.wasCopied() ? t().allCopiedAnnouncement : ""}
|
|
807
896
|
</span>
|
|
808
|
-
</
|
|
897
|
+
</ReaderContent>
|
|
809
898
|
</div>
|
|
810
899
|
);
|
|
811
900
|
};
|
|
@@ -839,7 +928,6 @@ export function LayoutHelpPage(props: LayoutHelpPageProps) {
|
|
|
839
928
|
<HelpShell
|
|
840
929
|
session={session}
|
|
841
930
|
close={() => {}}
|
|
842
|
-
searchHelpApps={[]}
|
|
843
931
|
documents={props.documents}
|
|
844
932
|
includeShortcuts={props.includeShortcuts}
|
|
845
933
|
accent={props.accent}
|
|
@@ -850,19 +938,12 @@ export function LayoutHelpPage(props: LayoutHelpPageProps) {
|
|
|
850
938
|
);
|
|
851
939
|
}
|
|
852
940
|
|
|
853
|
-
|
|
941
|
+
/** Open the current app's Help over the page; the dialog sits outside the app canvas, so it takes the app's accent along. */
|
|
942
|
+
export function openLayoutHelpDialog() {
|
|
854
943
|
const session = createSession();
|
|
944
|
+
const accent = readShellData()?.accent;
|
|
855
945
|
void prompts.dialog<void>(
|
|
856
|
-
(close) =>
|
|
857
|
-
<HelpShell
|
|
858
|
-
session={session}
|
|
859
|
-
close={close}
|
|
860
|
-
searchHelpApps={searchHelpApps}
|
|
861
|
-
accent={accent}
|
|
862
|
-
surface="modal"
|
|
863
|
-
pageBase={window.__cloudLayoutHelpPageBase}
|
|
864
|
-
/>
|
|
865
|
-
),
|
|
946
|
+
(close) => <HelpShell session={session} close={close} accent={accent} surface="modal" pageBase={window.__cloudLayoutHelpPageBase} />,
|
|
866
947
|
{ surface: "bare", header: false, size: "wide" },
|
|
867
948
|
);
|
|
868
949
|
}
|