@nextclaw/ui 0.12.35-beta.3 → 0.12.35-beta.5
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/CHANGELOG.md +126 -0
- package/dist/assets/api-BTZui7nj.js +15 -0
- package/dist/assets/{book-open--T9dJHnp.js → book-open-qc0UVn6m.js} +1 -1
- package/dist/assets/channels-list-page-DBtU_M1Y.js +8 -0
- package/dist/assets/{chat-page-DOKLoiqI.js → chat-page-BcoJYo91.js} +2 -2
- package/dist/assets/config-split-page-DW8tP9Bc.js +1 -0
- package/dist/assets/createLucideIcon-CA0sgoMJ.js +1 -0
- package/dist/assets/desktop-update-config-DARSwQG8.js +1 -0
- package/dist/assets/dist-D5zsy7pX.js +1 -0
- package/dist/assets/dist-DNxIa3pe.js +41 -0
- package/dist/assets/doc-browser-B9skxzVo.js +1 -0
- package/dist/assets/doc-browser-CwyITRXx.js +1 -0
- package/dist/assets/doc-browser-context-Do2qSrB8.js +1 -0
- package/dist/assets/{external-link-CZ9bQY0L.js → external-link-C9fbyi6k.js} +1 -1
- package/dist/assets/index-BYRP_BMj.css +1 -0
- package/dist/assets/index-DckPGTdM.js +103 -0
- package/dist/assets/mcp-marketplace-page-BiExW5wq.js +40 -0
- package/dist/assets/mcp-marketplace-page-Cj6MDSkE.js +1 -0
- package/dist/assets/model-config-DbdY7fF1.js +1 -0
- package/dist/assets/notice-card-CyiVcu_Z.js +5 -0
- package/dist/assets/play-D-s0C4MN.js +1 -0
- package/dist/assets/plus-DOwjM_kF.js +1 -0
- package/dist/assets/provider-scoped-model-input-Bk8jlpRm.js +1 -0
- package/dist/assets/providers-list-CJjNStDV.js +1 -0
- package/dist/assets/react-BaRotFd6.js +1 -0
- package/dist/assets/remote-gl8QckNc.js +1 -0
- package/dist/assets/runtime-config-page-BMFc1yzf.js +1 -0
- package/dist/assets/{save-B6wbBLkU.js → save-CG4Hb1u8.js} +1 -1
- package/dist/assets/{search-config-BFmG4_F7.js → search-config-DUOR26if.js} +1 -1
- package/dist/assets/{search-Czito3Dk.js → search-lWpOIHEn.js} +1 -1
- package/dist/assets/secrets-config-ZbLzjIRx.js +3 -0
- package/dist/assets/setting-row-CNfTKDjc.js +1 -0
- package/dist/assets/tabs-custom-BbZoysAl.js +1 -0
- package/dist/assets/tag-chip-Dl8JbhC7.js +1 -0
- package/dist/assets/x-D6GwT_8k.js +1 -0
- package/dist/index.html +15 -17
- package/package.json +8 -9
- package/src/app/components/app-presenter-provider.tsx +21 -0
- package/src/app/components/layout/sidebar.layout.test.tsx +2 -2
- package/src/app/components/layout/sidebar.tsx +3 -3
- package/src/app/index.tsx +5 -3
- package/src/app/presenters/app.presenter.ts +24 -0
- package/src/features/account/components/account-panel.tsx +11 -11
- package/src/features/account/index.ts +1 -4
- package/src/features/account/managers/account.manager.ts +52 -27
- package/src/features/account/stores/account.store.ts +0 -15
- package/src/features/apps/components/apps-panel.tsx +43 -0
- package/src/features/apps/index.ts +1 -0
- package/src/features/chat/components/layout/chat-sidebar-utility-menu.tsx +8 -8
- package/src/features/chat/components/layout/chat-sidebar.tsx +3 -2
- package/src/features/chat/managers/chat-session-list.manager.test.ts +58 -0
- package/src/features/chat/stores/chat-session-list.store.ts +58 -7
- package/src/features/panel-apps/components/panel-app-toolbar.tsx +4 -4
- package/src/features/panel-apps/components/panel-apps-list.tsx +6 -2
- package/src/features/panel-apps/index.ts +3 -1
- package/src/features/panel-apps/managers/panel-app-bridge.manager.test.ts +111 -0
- package/src/features/panel-apps/managers/panel-app-bridge.manager.ts +247 -0
- package/src/features/panel-apps/utils/panel-app-doc-browser.utils.test.ts +23 -0
- package/src/features/panel-apps/utils/panel-app-doc-browser.utils.tsx +42 -16
- package/src/features/remote/components/remote-access-page.test.tsx +2 -2
- package/src/features/remote/components/remote-access-page.tsx +9 -9
- package/src/features/remote/managers/remote-access.manager.ts +24 -41
- package/src/features/service-apps/components/service-action-authorization-dialog.tsx +78 -0
- package/src/features/service-apps/components/service-apps-panel.tsx +290 -0
- package/src/features/service-apps/hooks/use-service-apps.ts +66 -0
- package/src/features/service-apps/index.ts +3 -0
- package/src/features/service-apps/managers/service-action-authorization.manager.ts +10 -0
- package/src/features/service-apps/stores/service-action-authorization.store.ts +53 -0
- package/src/shared/components/doc-browser/doc-browser-context.test.tsx +150 -1
- package/src/shared/components/doc-browser/doc-browser-context.tsx +34 -443
- package/src/shared/components/doc-browser/doc-browser-renderer.types.ts +7 -0
- package/src/shared/components/doc-browser/doc-browser.tsx +16 -1
- package/src/shared/components/doc-browser/managers/doc-browser.manager.ts +272 -0
- package/src/shared/components/doc-browser/stores/doc-browser.store.ts +140 -0
- package/src/shared/components/doc-browser/types/doc-browser.types.ts +47 -0
- package/src/shared/components/doc-browser/utils/doc-browser-state.utils.ts +47 -0
- package/src/shared/components/doc-browser/utils/doc-browser-url.utils.ts +95 -0
- package/src/shared/lib/api/managers/client.manager.ts +2 -1
- package/src/shared/lib/i18n/runtime/doc-browser-labels.utils.ts +31 -0
- package/src/shared/lib/transport/local-transport.service.ts +18 -2
- package/src/shared/lib/transport/remote-transport.service.ts +19 -3
- package/src/shared/lib/transport/request-raw-api-response.utils.ts +1 -1
- package/dist/assets/api-D3uoaOR8.js +0 -15
- package/dist/assets/channels-list-page-DHmz9iHg.js +0 -8
- package/dist/assets/config-split-page-Dutq3R5o.js +0 -1
- package/dist/assets/createLucideIcon-Cwr2o6Zq.js +0 -1
- package/dist/assets/desktop-update-config-ClbDsIOs.js +0 -1
- package/dist/assets/dist-e5WATjIW.js +0 -1
- package/dist/assets/doc-browser-ChY6hMn8.js +0 -1
- package/dist/assets/doc-browser-cR1vWRs1.js +0 -1
- package/dist/assets/doc-browser-context-CLczyX2Z.js +0 -1
- package/dist/assets/es2015-BJw-hrex.js +0 -41
- package/dist/assets/i18n-tWKGEGxB.js +0 -1
- package/dist/assets/index-DPz9DRZN.js +0 -103
- package/dist/assets/index-HvJDzfSU.css +0 -1
- package/dist/assets/key-round-B8tTb3ZJ.js +0 -1
- package/dist/assets/mcp-marketplace-page-BYPxLdUR.js +0 -40
- package/dist/assets/mcp-marketplace-page-DXCvk7wJ.js +0 -1
- package/dist/assets/model-config-DcnC_fSr.js +0 -1
- package/dist/assets/notice-card-DUDbOvqx.js +0 -5
- package/dist/assets/play-CV_kwi9X.js +0 -1
- package/dist/assets/plus-BqfNq7Tu.js +0 -1
- package/dist/assets/provider-scoped-model-input-eO4XY0iY.js +0 -1
- package/dist/assets/providers-list-BgvjRVmp.js +0 -1
- package/dist/assets/remote-BCuZ1JJe.js +0 -1
- package/dist/assets/rotate-cw-BKEhWuWp.js +0 -1
- package/dist/assets/runtime-config-page-B2XPXHrk.js +0 -1
- package/dist/assets/secrets-config-4uY5sk-V.js +0 -3
- package/dist/assets/select-CmmDgG8o.js +0 -1
- package/dist/assets/setting-row-BUO6FeN1.js +0 -1
- package/dist/assets/tabs-custom-D3IZL9WX.js +0 -1
- package/dist/assets/tag-chip-Dm_pOtE3.js +0 -1
- package/dist/assets/x-DBP3WiC9.js +0 -1
- package/src/app/components/app-manager-provider.tsx +0 -20
- package/src/app/managers/app.manager.ts +0 -12
- /package/dist/assets/{config-hints-DGfwbumO.js → config-hints-CTqBnCDp.js} +0 -0
|
@@ -1,196 +1,25 @@
|
|
|
1
|
-
import { createContext,
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
export
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
id: string;
|
|
21
|
-
kind: DocBrowserTabKind;
|
|
22
|
-
title: string;
|
|
23
|
-
currentUrl: string;
|
|
24
|
-
dedupeKey?: string;
|
|
25
|
-
history: string[];
|
|
26
|
-
historyIndex: number;
|
|
27
|
-
/** Increments on parent-initiated navigation to trigger iframe remount */
|
|
28
|
-
navVersion: number;
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
export type DocBrowserOpenOptions = {
|
|
32
|
-
activate?: boolean;
|
|
33
|
-
dedupeKey?: string;
|
|
34
|
-
newTab?: boolean;
|
|
35
|
-
title?: string;
|
|
36
|
-
kind?: DocBrowserTabKind;
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
type DocBrowserState = {
|
|
40
|
-
isOpen: boolean;
|
|
41
|
-
mode: DocBrowserMode;
|
|
42
|
-
tabs: DocBrowserTab[];
|
|
43
|
-
activeTabId: string;
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
interface DocBrowserActions {
|
|
47
|
-
open: (url?: string, options?: DocBrowserOpenOptions) => void;
|
|
48
|
-
close: () => void;
|
|
49
|
-
toggleMode: () => void;
|
|
50
|
-
navigate: (url: string) => void;
|
|
51
|
-
syncUrl: (url: string) => void;
|
|
52
|
-
goBack: () => void;
|
|
53
|
-
goForward: () => void;
|
|
54
|
-
closeTab: (tabId: string) => void;
|
|
55
|
-
setActiveTab: (tabId: string) => void;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
export type DocBrowserContextValue = DocBrowserState & DocBrowserActions & {
|
|
59
|
-
currentTab?: DocBrowserTab;
|
|
60
|
-
};
|
|
1
|
+
import { createContext, useContext, useMemo, type ReactNode } from 'react';
|
|
2
|
+
import { DocBrowserManager } from '@/shared/components/doc-browser/managers/doc-browser.manager';
|
|
3
|
+
import { useDocBrowserStore } from '@/shared/components/doc-browser/stores/doc-browser.store';
|
|
4
|
+
import type { DocBrowserContextValue } from '@/shared/components/doc-browser/types/doc-browser.types';
|
|
5
|
+
|
|
6
|
+
export type {
|
|
7
|
+
DocBrowserActions,
|
|
8
|
+
DocBrowserContextValue,
|
|
9
|
+
DocBrowserMode,
|
|
10
|
+
DocBrowserOpenOptions,
|
|
11
|
+
DocBrowserState,
|
|
12
|
+
DocBrowserStateUpdate,
|
|
13
|
+
DocBrowserTab,
|
|
14
|
+
DocBrowserTabKind,
|
|
15
|
+
} from '@/shared/components/doc-browser/types/doc-browser.types';
|
|
16
|
+
export {
|
|
17
|
+
DOCS_DEFAULT_BASE_URL,
|
|
18
|
+
isDocsUrl,
|
|
19
|
+
} from '@/shared/components/doc-browser/utils/doc-browser-url.utils';
|
|
61
20
|
|
|
62
21
|
const DocBrowserContext = createContext<DocBrowserContextValue | null>(null);
|
|
63
|
-
|
|
64
|
-
let tabCounter = 0;
|
|
65
|
-
|
|
66
|
-
function nextTabId(): string {
|
|
67
|
-
tabCounter += 1;
|
|
68
|
-
return `doc-tab-${Date.now()}-${tabCounter}`;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/** Normalize URL for comparison: strip .html and trailing slash */
|
|
72
|
-
function normalizeDocUrl(u: string): string {
|
|
73
|
-
try {
|
|
74
|
-
return new URL(u).pathname.replace(/\.html$/, '').replace(/\/$/, '');
|
|
75
|
-
} catch {
|
|
76
|
-
return u;
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
function toDocsLocale(language: I18nLanguage): 'en' | 'zh' {
|
|
81
|
-
return language === 'zh' ? 'zh' : 'en';
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
function ensureLocalizedDocsPath(pathname: string, locale: 'en' | 'zh'): string {
|
|
85
|
-
const normalized = pathname.startsWith('/') ? pathname : `/${pathname}`;
|
|
86
|
-
|
|
87
|
-
if (normalized === '/' || normalized === '') {
|
|
88
|
-
return `/${locale}/`;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
if (/^\/(en|zh)(\/|$)/.test(normalized)) {
|
|
92
|
-
return normalized;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
return `/${locale}${normalized}`;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
function resolveLocalizedDocsUrl(url: string): string {
|
|
99
|
-
const locale = toDocsLocale(getLanguage());
|
|
100
|
-
|
|
101
|
-
try {
|
|
102
|
-
const parsed = new URL(url, DOCS_DEFAULT_BASE_URL);
|
|
103
|
-
if (!DOCS_HOSTS.has(parsed.hostname)) {
|
|
104
|
-
return parsed.toString();
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
parsed.pathname = ensureLocalizedDocsPath(parsed.pathname, locale);
|
|
108
|
-
return parsed.toString();
|
|
109
|
-
} catch {
|
|
110
|
-
return new URL(`/${locale}${DOCS_DEFAULT_GUIDE_PATH}`, DOCS_DEFAULT_BASE_URL).toString();
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
function getDefaultDocsUrl(): string {
|
|
115
|
-
return resolveLocalizedDocsUrl(DOCS_DEFAULT_GUIDE_PATH);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
function inferTabTitle(url: string, kind: DocBrowserTabKind, fallback = 'Docs'): string {
|
|
119
|
-
try {
|
|
120
|
-
const parsed = new URL(url, DOCS_DEFAULT_BASE_URL);
|
|
121
|
-
if (parsed.protocol === 'data:') {
|
|
122
|
-
return kind === 'docs' ? fallback : 'Detail';
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
const segments = parsed.pathname.split('/').filter(Boolean);
|
|
126
|
-
const leaf = segments[segments.length - 1] ?? fallback;
|
|
127
|
-
return decodeURIComponent(leaf).replace(/[-_]/g, ' ').slice(0, 40) || fallback;
|
|
128
|
-
} catch {
|
|
129
|
-
return fallback;
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
function createTab(
|
|
134
|
-
url: string,
|
|
135
|
-
kind: DocBrowserTabKind,
|
|
136
|
-
title?: string,
|
|
137
|
-
dedupeKey?: string,
|
|
138
|
-
): DocBrowserTab {
|
|
139
|
-
const tabTitle = title?.trim() || inferTabTitle(url, kind, kind === 'docs' ? 'Docs' : 'Detail');
|
|
140
|
-
|
|
141
|
-
return {
|
|
142
|
-
id: nextTabId(),
|
|
143
|
-
kind,
|
|
144
|
-
title: tabTitle,
|
|
145
|
-
currentUrl: url,
|
|
146
|
-
dedupeKey,
|
|
147
|
-
history: [url],
|
|
148
|
-
historyIndex: 0,
|
|
149
|
-
navVersion: 0,
|
|
150
|
-
};
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
function updateActiveTab(state: DocBrowserState, updater: (tab: DocBrowserTab) => DocBrowserTab): DocBrowserState {
|
|
154
|
-
return updateTab(state, state.activeTabId, updater);
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
function updateTab(
|
|
158
|
-
state: DocBrowserState,
|
|
159
|
-
tabId: string,
|
|
160
|
-
updater: (tab: DocBrowserTab) => DocBrowserTab,
|
|
161
|
-
): DocBrowserState {
|
|
162
|
-
return {
|
|
163
|
-
...state,
|
|
164
|
-
tabs: state.tabs.map((tab) => (tab.id === tabId ? updater(tab) : tab)),
|
|
165
|
-
};
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
function updateTabForOpen(
|
|
169
|
-
tab: DocBrowserTab,
|
|
170
|
-
url: string,
|
|
171
|
-
kind: DocBrowserTabKind,
|
|
172
|
-
options?: DocBrowserOpenOptions,
|
|
173
|
-
dedupeKey?: string,
|
|
174
|
-
): DocBrowserTab {
|
|
175
|
-
const baseTab = {
|
|
176
|
-
...tab,
|
|
177
|
-
title: options?.title || tab.title,
|
|
178
|
-
kind,
|
|
179
|
-
dedupeKey,
|
|
180
|
-
};
|
|
181
|
-
|
|
182
|
-
if (normalizeDocUrl(url) === normalizeDocUrl(tab.currentUrl)) {
|
|
183
|
-
return baseTab;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
return {
|
|
187
|
-
...baseTab,
|
|
188
|
-
currentUrl: url,
|
|
189
|
-
history: [...tab.history.slice(0, tab.historyIndex + 1), url],
|
|
190
|
-
historyIndex: tab.historyIndex + 1,
|
|
191
|
-
navVersion: tab.navVersion + 1,
|
|
192
|
-
};
|
|
193
|
-
}
|
|
22
|
+
const docBrowserManager = new DocBrowserManager();
|
|
194
23
|
|
|
195
24
|
export function useDocBrowser(): DocBrowserContextValue {
|
|
196
25
|
const ctx = useContext(DocBrowserContext);
|
|
@@ -198,263 +27,25 @@ export function useDocBrowser(): DocBrowserContextValue {
|
|
|
198
27
|
return ctx;
|
|
199
28
|
}
|
|
200
29
|
|
|
201
|
-
/** Check if a URL belongs to the docs domain */
|
|
202
|
-
export function isDocsUrl(url: string): boolean {
|
|
203
|
-
try {
|
|
204
|
-
const parsed = new URL(url, window.location.origin);
|
|
205
|
-
return DOCS_HOSTS.has(parsed.hostname);
|
|
206
|
-
} catch {
|
|
207
|
-
return false;
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
function inferTabKind(url: string): DocBrowserTabKind {
|
|
212
|
-
return isDocsUrl(url) ? 'docs' : 'content';
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
function normalizeUrlByKind(url: string, kind: DocBrowserTabKind): string {
|
|
216
|
-
if (kind === 'docs') {
|
|
217
|
-
return resolveLocalizedDocsUrl(url);
|
|
218
|
-
}
|
|
219
|
-
return url;
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
function resolveOpenTargetUrl(params: {
|
|
223
|
-
url?: string;
|
|
224
|
-
kind: DocBrowserTabKind;
|
|
225
|
-
activeTab?: DocBrowserTab;
|
|
226
|
-
}): string {
|
|
227
|
-
const { activeTab, kind, url } = params;
|
|
228
|
-
if (url && url.trim().length > 0) {
|
|
229
|
-
return normalizeUrlByKind(url, kind);
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
if (kind === 'docs') {
|
|
233
|
-
return getDefaultDocsUrl();
|
|
234
|
-
}
|
|
235
|
-
return activeTab?.currentUrl ?? getDefaultDocsUrl();
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
function openDocBrowserState(
|
|
239
|
-
prev: DocBrowserState,
|
|
240
|
-
url?: string,
|
|
241
|
-
options?: DocBrowserOpenOptions,
|
|
242
|
-
): DocBrowserState {
|
|
243
|
-
const {
|
|
244
|
-
activate,
|
|
245
|
-
dedupeKey: rawDedupeKey,
|
|
246
|
-
kind,
|
|
247
|
-
newTab: shouldForceNewTab,
|
|
248
|
-
title,
|
|
249
|
-
} = options ?? {};
|
|
250
|
-
const activeTab = prev.tabs.find((tab) => tab.id === prev.activeTabId) ?? prev.tabs[0];
|
|
251
|
-
const targetKind = kind ?? (url ? inferTabKind(url) : activeTab?.kind ?? 'docs');
|
|
252
|
-
const targetUrl = resolveOpenTargetUrl({ url, kind: targetKind, activeTab });
|
|
253
|
-
const dedupeKey = rawDedupeKey?.trim() || undefined;
|
|
254
|
-
const matchedTab = dedupeKey
|
|
255
|
-
? prev.tabs.find((tab) => tab.dedupeKey === dedupeKey)
|
|
256
|
-
: undefined;
|
|
257
|
-
|
|
258
|
-
if (matchedTab) {
|
|
259
|
-
const next = updateTab(
|
|
260
|
-
prev,
|
|
261
|
-
matchedTab.id,
|
|
262
|
-
(tab) => updateTabForOpen(tab, targetUrl, targetKind, options, dedupeKey),
|
|
263
|
-
);
|
|
264
|
-
return {
|
|
265
|
-
...next,
|
|
266
|
-
activeTabId: activate === false ? prev.activeTabId : matchedTab.id,
|
|
267
|
-
isOpen: true,
|
|
268
|
-
};
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
if (shouldForceNewTab || dedupeKey || !activeTab || activeTab.kind !== targetKind) {
|
|
272
|
-
const newTab = createTab(targetUrl, targetKind, title, dedupeKey);
|
|
273
|
-
return {
|
|
274
|
-
...prev,
|
|
275
|
-
isOpen: true,
|
|
276
|
-
tabs: [...prev.tabs, newTab],
|
|
277
|
-
activeTabId: newTab.id,
|
|
278
|
-
};
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
const next = updateActiveTab(
|
|
282
|
-
prev,
|
|
283
|
-
(tab) => updateTabForOpen(tab, targetUrl, targetKind, options, dedupeKey),
|
|
284
|
-
);
|
|
285
|
-
return { ...next, isOpen: true };
|
|
286
|
-
}
|
|
287
|
-
|
|
288
30
|
export function DocBrowserProvider({ children }: { children: ReactNode }) {
|
|
289
|
-
const
|
|
290
|
-
const initialTab = createTab(initialUrl, 'docs', 'Docs');
|
|
291
|
-
|
|
292
|
-
const [state, setState] = useState<DocBrowserState>({
|
|
293
|
-
isOpen: false,
|
|
294
|
-
mode: 'docked',
|
|
295
|
-
tabs: [initialTab],
|
|
296
|
-
activeTabId: initialTab.id,
|
|
297
|
-
});
|
|
298
|
-
|
|
31
|
+
const snapshot = useDocBrowserStore((state) => state.snapshot);
|
|
299
32
|
const currentTab = useMemo(() => {
|
|
300
|
-
return
|
|
301
|
-
}, [
|
|
302
|
-
|
|
303
|
-
const open = useCallback((url?: string, options?: DocBrowserOpenOptions) => {
|
|
304
|
-
setState((prev) => openDocBrowserState(prev, url, options));
|
|
305
|
-
}, []);
|
|
306
|
-
|
|
307
|
-
const close = useCallback(() => {
|
|
308
|
-
setState((prev) => ({ ...prev, isOpen: false }));
|
|
309
|
-
}, []);
|
|
310
|
-
|
|
311
|
-
const toggleMode = useCallback(() => {
|
|
312
|
-
setState((prev) => ({ ...prev, mode: prev.mode === 'floating' ? 'docked' : 'floating' }));
|
|
313
|
-
}, []);
|
|
314
|
-
|
|
315
|
-
const navigate = useCallback((url: string) => {
|
|
316
|
-
setState((prev) => {
|
|
317
|
-
if (!prev.tabs.length) {
|
|
318
|
-
const fallbackTab = createTab(getDefaultDocsUrl(), 'docs', 'Docs');
|
|
319
|
-
return {
|
|
320
|
-
...prev,
|
|
321
|
-
tabs: [fallbackTab],
|
|
322
|
-
activeTabId: fallbackTab.id,
|
|
323
|
-
isOpen: true,
|
|
324
|
-
};
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
return updateActiveTab(prev, (tab) => {
|
|
328
|
-
if (tab.kind !== 'docs') {
|
|
329
|
-
return tab;
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
const targetUrl = normalizeUrlByKind(url, 'docs');
|
|
333
|
-
if (normalizeDocUrl(targetUrl) === normalizeDocUrl(tab.currentUrl)) {
|
|
334
|
-
return tab;
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
return {
|
|
338
|
-
...tab,
|
|
339
|
-
currentUrl: targetUrl,
|
|
340
|
-
history: [...tab.history.slice(0, tab.historyIndex + 1), targetUrl],
|
|
341
|
-
historyIndex: tab.historyIndex + 1,
|
|
342
|
-
navVersion: tab.navVersion + 1,
|
|
343
|
-
};
|
|
344
|
-
});
|
|
345
|
-
});
|
|
346
|
-
}, []);
|
|
347
|
-
|
|
348
|
-
const syncUrl = useCallback((url: string) => {
|
|
349
|
-
setState((prev) => {
|
|
350
|
-
if (!prev.tabs.length) {
|
|
351
|
-
const fallbackTab = createTab(getDefaultDocsUrl(), 'docs', 'Docs');
|
|
352
|
-
return {
|
|
353
|
-
...prev,
|
|
354
|
-
tabs: [fallbackTab],
|
|
355
|
-
activeTabId: fallbackTab.id,
|
|
356
|
-
};
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
return updateActiveTab(prev, (tab) => {
|
|
360
|
-
if (tab.kind !== 'docs') {
|
|
361
|
-
return tab;
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
if (normalizeDocUrl(url) === normalizeDocUrl(tab.currentUrl)) {
|
|
365
|
-
return tab;
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
return {
|
|
369
|
-
...tab,
|
|
370
|
-
currentUrl: url,
|
|
371
|
-
history: [...tab.history.slice(0, tab.historyIndex + 1), url],
|
|
372
|
-
historyIndex: tab.historyIndex + 1,
|
|
373
|
-
};
|
|
374
|
-
});
|
|
375
|
-
});
|
|
376
|
-
}, []);
|
|
377
|
-
|
|
378
|
-
const goBack = useCallback(() => {
|
|
379
|
-
setState((prev) => updateActiveTab(prev, (tab) => {
|
|
380
|
-
if (tab.kind !== 'docs' || tab.historyIndex <= 0) return tab;
|
|
381
|
-
const newIndex = tab.historyIndex - 1;
|
|
382
|
-
return { ...tab, historyIndex: newIndex, currentUrl: tab.history[newIndex] };
|
|
383
|
-
}));
|
|
384
|
-
}, []);
|
|
385
|
-
|
|
386
|
-
const goForward = useCallback(() => {
|
|
387
|
-
setState((prev) => updateActiveTab(prev, (tab) => {
|
|
388
|
-
if (tab.kind !== 'docs' || tab.historyIndex >= tab.history.length - 1) return tab;
|
|
389
|
-
const newIndex = tab.historyIndex + 1;
|
|
390
|
-
return { ...tab, historyIndex: newIndex, currentUrl: tab.history[newIndex] };
|
|
391
|
-
}));
|
|
392
|
-
}, []);
|
|
393
|
-
|
|
394
|
-
const closeTab = useCallback((tabId: string) => {
|
|
395
|
-
setState((prev) => {
|
|
396
|
-
if (prev.tabs.length <= 1) {
|
|
397
|
-
const fallbackTab = createTab(getDefaultDocsUrl(), 'docs', 'Docs');
|
|
398
|
-
return {
|
|
399
|
-
...prev,
|
|
400
|
-
isOpen: false,
|
|
401
|
-
tabs: [fallbackTab],
|
|
402
|
-
activeTabId: fallbackTab.id,
|
|
403
|
-
};
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
const index = prev.tabs.findIndex((tab) => tab.id === tabId);
|
|
407
|
-
if (index < 0) {
|
|
408
|
-
return prev;
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
const nextTabs = prev.tabs.filter((tab) => tab.id !== tabId);
|
|
412
|
-
const nextActiveId = prev.activeTabId === tabId
|
|
413
|
-
? nextTabs[Math.max(0, index - 1)]?.id ?? nextTabs[0].id
|
|
414
|
-
: prev.activeTabId;
|
|
415
|
-
|
|
416
|
-
return {
|
|
417
|
-
...prev,
|
|
418
|
-
tabs: nextTabs,
|
|
419
|
-
activeTabId: nextActiveId,
|
|
420
|
-
};
|
|
421
|
-
});
|
|
422
|
-
}, []);
|
|
423
|
-
|
|
424
|
-
const setActiveTab = useCallback((tabId: string) => {
|
|
425
|
-
setState((prev) => {
|
|
426
|
-
if (!prev.tabs.some((tab) => tab.id === tabId)) {
|
|
427
|
-
return prev;
|
|
428
|
-
}
|
|
429
|
-
return { ...prev, activeTabId: tabId, isOpen: true };
|
|
430
|
-
});
|
|
431
|
-
}, []);
|
|
33
|
+
return snapshot.tabs.find((tab) => tab.id === snapshot.activeTabId) ?? snapshot.tabs[0];
|
|
34
|
+
}, [snapshot.activeTabId, snapshot.tabs]);
|
|
432
35
|
|
|
433
36
|
const value = useMemo<DocBrowserContextValue>(() => ({
|
|
434
|
-
...
|
|
435
|
-
currentTab,
|
|
436
|
-
open,
|
|
437
|
-
close,
|
|
438
|
-
toggleMode,
|
|
439
|
-
navigate,
|
|
440
|
-
syncUrl,
|
|
441
|
-
goBack,
|
|
442
|
-
goForward,
|
|
443
|
-
closeTab,
|
|
444
|
-
setActiveTab,
|
|
445
|
-
}), [
|
|
446
|
-
state,
|
|
37
|
+
...snapshot,
|
|
447
38
|
currentTab,
|
|
448
|
-
open,
|
|
449
|
-
close,
|
|
450
|
-
toggleMode,
|
|
451
|
-
navigate,
|
|
452
|
-
syncUrl,
|
|
453
|
-
goBack,
|
|
454
|
-
goForward,
|
|
455
|
-
closeTab,
|
|
456
|
-
setActiveTab,
|
|
457
|
-
]);
|
|
39
|
+
open: docBrowserManager.open,
|
|
40
|
+
close: docBrowserManager.close,
|
|
41
|
+
toggleMode: docBrowserManager.toggleMode,
|
|
42
|
+
navigate: docBrowserManager.navigate,
|
|
43
|
+
syncUrl: docBrowserManager.syncUrl,
|
|
44
|
+
goBack: docBrowserManager.goBack,
|
|
45
|
+
goForward: docBrowserManager.goForward,
|
|
46
|
+
closeTab: docBrowserManager.closeTab,
|
|
47
|
+
setActiveTab: docBrowserManager.setActiveTab,
|
|
48
|
+
}), [currentTab, snapshot]);
|
|
458
49
|
|
|
459
50
|
return (
|
|
460
51
|
<DocBrowserContext.Provider value={value}>
|
|
@@ -8,9 +8,16 @@ export type DocBrowserCustomTabRenderParams = {
|
|
|
8
8
|
tab: DocBrowserTab;
|
|
9
9
|
};
|
|
10
10
|
|
|
11
|
+
export type DocBrowserIframeMessageParams = {
|
|
12
|
+
event: MessageEvent;
|
|
13
|
+
iframe: HTMLIFrameElement | null;
|
|
14
|
+
tab: DocBrowserTab;
|
|
15
|
+
};
|
|
16
|
+
|
|
11
17
|
export type DocBrowserCustomTabRenderer = {
|
|
12
18
|
getIframeSandbox?: (tab: DocBrowserTab) => string;
|
|
13
19
|
getTitle?: (tab: DocBrowserTab) => string;
|
|
20
|
+
onIframeMessage?: (params: DocBrowserIframeMessageParams) => void;
|
|
14
21
|
renderContent?: (params: DocBrowserCustomTabRenderParams) => ReactNode;
|
|
15
22
|
renderIcon?: (tab: DocBrowserTab) => ReactNode;
|
|
16
23
|
renderToolbar?: (params: DocBrowserCustomTabRenderParams) => ReactNode;
|
|
@@ -73,6 +73,7 @@ export function DocBrowser({
|
|
|
73
73
|
const navVersion = currentTab?.navVersion ?? 0;
|
|
74
74
|
const prevNavVersionRef = useRef(navVersion);
|
|
75
75
|
const isDocsTab = currentTab?.kind === 'docs';
|
|
76
|
+
const customRenderer = currentTab ? customTabRenderers[currentTab.kind] : undefined;
|
|
76
77
|
|
|
77
78
|
useEffect(() => {
|
|
78
79
|
if (!isDocsTab) {
|
|
@@ -121,6 +122,21 @@ export function DocBrowser({
|
|
|
121
122
|
return () => window.removeEventListener('message', handler);
|
|
122
123
|
}, [syncUrl, isDocsTab]);
|
|
123
124
|
|
|
125
|
+
useEffect(() => {
|
|
126
|
+
if (!currentTab || !customRenderer?.onIframeMessage) {
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
const handler = (event: MessageEvent) => {
|
|
130
|
+
customRenderer.onIframeMessage?.({
|
|
131
|
+
event,
|
|
132
|
+
iframe: iframeRef.current,
|
|
133
|
+
tab: currentTab,
|
|
134
|
+
});
|
|
135
|
+
};
|
|
136
|
+
window.addEventListener('message', handler);
|
|
137
|
+
return () => window.removeEventListener('message', handler);
|
|
138
|
+
}, [currentTab, customRenderer]);
|
|
139
|
+
|
|
124
140
|
const startFloatDrag = useCallback((event: React.PointerEvent<HTMLElement>) => {
|
|
125
141
|
event.preventDefault();
|
|
126
142
|
setFloatInteraction({
|
|
@@ -213,7 +229,6 @@ export function DocBrowser({
|
|
|
213
229
|
|
|
214
230
|
const isDocked = mode === 'docked';
|
|
215
231
|
const isFullscreen = displayMode === 'fullscreen';
|
|
216
|
-
const customRenderer = currentTab ? customTabRenderers[currentTab.kind] : undefined;
|
|
217
232
|
const customRenderParams = currentTab ? {
|
|
218
233
|
currentUrl,
|
|
219
234
|
open,
|