@nextclaw/ui 0.12.35-beta.6 → 0.12.35
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 +482 -0
- package/dist/assets/api-DruAcori.js +15 -0
- package/dist/assets/{book-open-qc0UVn6m.js → book-open-cPX0Za7Z.js} +1 -1
- package/dist/assets/channels-list-page-DXH1qYLs.js +8 -0
- package/dist/assets/chat-page-Cpo0AFHb.js +105 -0
- package/dist/assets/{config-split-page-DW8tP9Bc.js → config-split-page-BFh_Qixx.js} +1 -1
- package/dist/assets/confirm-dialog-B0TP-vPd.js +5 -0
- package/dist/assets/{createLucideIcon-CA0sgoMJ.js → createLucideIcon-Br1EFMEr.js} +1 -1
- package/dist/assets/{desktop-update-config-DARSwQG8.js → desktop-update-config-Be8x1jNy.js} +1 -1
- package/dist/assets/{dist-D5zsy7pX.js → dist-CpwDZDtv.js} +1 -1
- package/dist/assets/{dist-DNxIa3pe.js → dist-rzakR_bu.js} +1 -1
- package/dist/assets/doc-browser-BI7HSaMK.js +3 -0
- package/dist/assets/doc-browser-context-DB6TTHeE.js +1 -0
- package/dist/assets/doc-browser-route-registry.utils-x5ijvk0u.js +1 -0
- package/dist/assets/doc-browser-yTfN7UBH.js +1 -0
- package/dist/assets/{external-link-C9fbyi6k.js → external-link-C3usm3se.js} +1 -1
- package/dist/assets/index-GVTiCf1s.js +101 -0
- package/dist/assets/index-v0fAGXQW.css +1 -0
- package/dist/assets/{mcp-marketplace-page-BiExW5wq.js → mcp-marketplace-page-DDjeHd1I.js} +2 -2
- package/dist/assets/mcp-marketplace-page-WUkc7n94.js +1 -0
- package/dist/assets/model-config-BjXMpU9U.js +1 -0
- package/dist/assets/play-CqNi0u02.js +1 -0
- package/dist/assets/plus-BcIC8dG1.js +1 -0
- package/dist/assets/{provider-scoped-model-input-Bk8jlpRm.js → provider-scoped-model-input-t1tCP5y5.js} +1 -1
- package/dist/assets/providers-list-C1D5p9kK.js +1 -0
- package/dist/assets/react-B2X_ph0a.js +1 -0
- package/dist/assets/remote-BHeENKxL.js +1 -0
- package/dist/assets/runtime-config-page--O0Wq01S.js +1 -0
- package/dist/assets/{save-CG4Hb1u8.js → save-D10M5vQg.js} +1 -1
- package/dist/assets/{search-lWpOIHEn.js → search-CdhgFNwf.js} +1 -1
- package/dist/assets/search-config-Dzpizo-_.js +1 -0
- package/dist/assets/{secrets-config-ZbLzjIRx.js → secrets-config-D1AUSEXX.js} +1 -1
- package/dist/assets/{setting-row-CNfTKDjc.js → setting-row-8VkukJoH.js} +1 -1
- package/dist/assets/{tabs-custom-BbZoysAl.js → tabs-custom-BBXu1OGs.js} +1 -1
- package/dist/assets/{tag-chip-Dl8JbhC7.js → tag-chip-ix15P-r4.js} +1 -1
- package/dist/assets/wrench-qVIwShTF.js +1 -0
- package/dist/assets/x-Chb07O-Y.js +1 -0
- package/dist/index.html +17 -15
- package/package.json +8 -8
- package/src/features/panel-apps/components/panel-app-list-item.test.tsx +60 -0
- package/src/features/panel-apps/components/panel-app-list-item.tsx +120 -30
- package/src/features/panel-apps/components/panel-apps-list.tsx +8 -1
- package/src/features/panel-apps/hooks/use-panel-apps.ts +10 -0
- package/src/features/panel-apps/managers/panel-app-bridge.manager.test.ts +153 -1
- package/src/features/panel-apps/managers/panel-app-bridge.manager.ts +126 -9
- package/src/features/panel-apps/utils/panel-app-doc-browser.utils.tsx +7 -3
- package/src/features/panel-apps/utils/panel-app-view.utils.test.ts +66 -0
- package/src/features/panel-apps/utils/panel-app-view.utils.ts +13 -2
- package/src/index.css +23 -0
- package/src/shared/components/doc-browser/doc-browser-context.test.tsx +170 -0
- package/src/shared/components/doc-browser/doc-browser-context.tsx +3 -0
- package/src/shared/components/doc-browser/doc-browser-home-page.tsx +95 -0
- package/src/shared/components/doc-browser/doc-browser-panel-parts.tsx +3 -97
- package/src/shared/components/doc-browser/doc-browser-renderer.types.ts +1 -0
- package/src/shared/components/doc-browser/doc-browser-tab-strip.tsx +117 -44
- package/src/shared/components/doc-browser/doc-browser.test.tsx +159 -1
- package/src/shared/components/doc-browser/doc-browser.tsx +46 -27
- package/src/shared/components/doc-browser/managers/doc-browser.manager.ts +183 -71
- package/src/shared/components/doc-browser/stores/doc-browser.store.ts +52 -1
- package/src/shared/components/doc-browser/types/doc-browser.types.ts +9 -0
- package/src/shared/components/doc-browser/utils/doc-browser-route-registry.utils.ts +232 -0
- package/src/shared/components/doc-browser/utils/doc-browser-state.utils.ts +11 -0
- package/src/shared/components/doc-browser/utils/doc-browser-url.utils.ts +2 -0
- package/src/shared/lib/api/types.ts +1 -0
- package/src/shared/lib/i18n/runtime/doc-browser-labels.utils.ts +10 -0
- package/dist/assets/api-BTZui7nj.js +0 -15
- package/dist/assets/channels-list-page-DBtU_M1Y.js +0 -8
- package/dist/assets/chat-page-BcoJYo91.js +0 -105
- package/dist/assets/doc-browser-B9skxzVo.js +0 -1
- package/dist/assets/doc-browser-CwyITRXx.js +0 -1
- package/dist/assets/doc-browser-context-Do2qSrB8.js +0 -1
- package/dist/assets/index-BYRP_BMj.css +0 -1
- package/dist/assets/index-DckPGTdM.js +0 -103
- package/dist/assets/mcp-marketplace-page-Cj6MDSkE.js +0 -1
- package/dist/assets/model-config-DbdY7fF1.js +0 -1
- package/dist/assets/notice-card-CyiVcu_Z.js +0 -5
- package/dist/assets/play-D-s0C4MN.js +0 -1
- package/dist/assets/plus-DOwjM_kF.js +0 -1
- package/dist/assets/providers-list-CJjNStDV.js +0 -1
- package/dist/assets/react-BaRotFd6.js +0 -1
- package/dist/assets/remote-gl8QckNc.js +0 -1
- package/dist/assets/runtime-config-page-BMFc1yzf.js +0 -1
- package/dist/assets/search-config-DUOR26if.js +0 -1
- package/dist/assets/x-D6GwT_8k.js +0 -1
- /package/dist/assets/{config-hints-CTqBnCDp.js → config-hints-CY1lg9IE.js} +0 -0
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
import { useState, useRef, useCallback, useEffect } from 'react';
|
|
2
2
|
import {
|
|
3
3
|
DOCS_DEFAULT_BASE_URL,
|
|
4
|
+
DOC_BROWSER_HOME_TAB_KIND,
|
|
4
5
|
useDocBrowser,
|
|
5
6
|
} from './doc-browser-context';
|
|
7
|
+
import { normalizeDocUrl } from './utils/doc-browser-url.utils';
|
|
8
|
+
import { DocBrowserHomePage } from './doc-browser-home-page';
|
|
6
9
|
import type { DocBrowserCustomTabRenderers } from './doc-browser-renderer.types';
|
|
7
10
|
import {
|
|
8
11
|
DocBrowserDocsToolbar,
|
|
9
12
|
DocBrowserExternalLink,
|
|
10
13
|
DocBrowserFrameContent,
|
|
11
|
-
DocBrowserPanelHeader,
|
|
12
14
|
} from './doc-browser-panel-parts';
|
|
13
15
|
import { DocBrowserTabStrip } from './doc-browser-tab-strip';
|
|
14
16
|
import { ResizableRightPanel } from '@/shared/components/resizable-right-panel/resizable-right-panel';
|
|
@@ -38,6 +40,15 @@ function clamp(value: number, min: number, max: number): number {
|
|
|
38
40
|
return Math.min(max, Math.max(min, value));
|
|
39
41
|
}
|
|
40
42
|
|
|
43
|
+
function createInitialFloatingPanelRect(): FloatingPanelRect {
|
|
44
|
+
return {
|
|
45
|
+
x: Math.max(FLOATING_PANEL_MARGIN, window.innerWidth - 520),
|
|
46
|
+
y: 80,
|
|
47
|
+
w: 480,
|
|
48
|
+
h: 600,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
41
52
|
export function DocBrowser({
|
|
42
53
|
customTabRenderers = {},
|
|
43
54
|
displayMode = 'desktop',
|
|
@@ -47,8 +58,11 @@ export function DocBrowser({
|
|
|
47
58
|
mode,
|
|
48
59
|
tabs,
|
|
49
60
|
activeTabId,
|
|
61
|
+
activeHistory,
|
|
62
|
+
activeHistoryIndex,
|
|
50
63
|
currentTab,
|
|
51
64
|
open,
|
|
65
|
+
openNewTab,
|
|
52
66
|
close,
|
|
53
67
|
toggleMode,
|
|
54
68
|
goBack,
|
|
@@ -61,18 +75,18 @@ export function DocBrowser({
|
|
|
61
75
|
|
|
62
76
|
const [urlInput, setUrlInput] = useState('');
|
|
63
77
|
const [iframeReloadVersion, setIframeReloadVersion] = useState(0);
|
|
64
|
-
const [floatRect, setFloatRect] = useState<FloatingPanelRect>(
|
|
65
|
-
x: Math.max(FLOATING_PANEL_MARGIN, window.innerWidth - 520),
|
|
66
|
-
y: 80,
|
|
67
|
-
w: 480,
|
|
68
|
-
h: 600,
|
|
69
|
-
}));
|
|
78
|
+
const [floatRect, setFloatRect] = useState<FloatingPanelRect>(createInitialFloatingPanelRect);
|
|
70
79
|
const [floatInteraction, setFloatInteraction] = useState<FloatingPanelInteraction | null>(null);
|
|
71
80
|
const iframeRef = useRef<HTMLIFrameElement>(null);
|
|
72
81
|
const currentUrl = currentTab?.currentUrl ?? DOCS_DEFAULT_BASE_URL;
|
|
73
82
|
const navVersion = currentTab?.navVersion ?? 0;
|
|
83
|
+
const iframeInstanceId = `${activeTabId}:${navVersion}:${iframeReloadVersion}`;
|
|
84
|
+
const pendingParentDocsUrlRef = useRef<string | null>(null);
|
|
74
85
|
const prevNavVersionRef = useRef(navVersion);
|
|
75
86
|
const isDocsTab = currentTab?.kind === 'docs';
|
|
87
|
+
const isHomeTab = currentTab?.kind === DOC_BROWSER_HOME_TAB_KIND;
|
|
88
|
+
const canGoBack = activeHistoryIndex > 0;
|
|
89
|
+
const canGoForward = activeHistoryIndex < activeHistory.length - 1;
|
|
76
90
|
const customRenderer = currentTab ? customTabRenderers[currentTab.kind] : undefined;
|
|
77
91
|
|
|
78
92
|
useEffect(() => {
|
|
@@ -92,16 +106,19 @@ export function DocBrowser({
|
|
|
92
106
|
// use postMessage to SPA-navigate inside the iframe instead of remounting.
|
|
93
107
|
useEffect(() => {
|
|
94
108
|
if (!isDocsTab) {
|
|
109
|
+
pendingParentDocsUrlRef.current = null;
|
|
95
110
|
return;
|
|
96
111
|
}
|
|
97
112
|
if (navVersion !== prevNavVersionRef.current) {
|
|
98
113
|
prevNavVersionRef.current = navVersion;
|
|
114
|
+
pendingParentDocsUrlRef.current = normalizeDocUrl(currentUrl);
|
|
99
115
|
return;
|
|
100
116
|
}
|
|
101
117
|
|
|
102
118
|
if (iframeRef.current?.contentWindow) {
|
|
103
119
|
try {
|
|
104
120
|
const path = new URL(currentUrl).pathname;
|
|
121
|
+
pendingParentDocsUrlRef.current = normalizeDocUrl(currentUrl);
|
|
105
122
|
iframeRef.current.contentWindow.postMessage({ type: 'docs-navigate', path }, '*');
|
|
106
123
|
} catch {
|
|
107
124
|
// ignore postMessage errors
|
|
@@ -115,12 +132,17 @@ export function DocBrowser({
|
|
|
115
132
|
return;
|
|
116
133
|
}
|
|
117
134
|
if (e.data?.type === 'docs-route-change' && typeof e.data.url === 'string') {
|
|
135
|
+
const eventUrl = normalizeDocUrl(e.data.url);
|
|
136
|
+
if (pendingParentDocsUrlRef.current && eventUrl !== pendingParentDocsUrlRef.current) {
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
pendingParentDocsUrlRef.current = null;
|
|
118
140
|
syncUrl(e.data.url);
|
|
119
141
|
}
|
|
120
142
|
};
|
|
121
143
|
window.addEventListener('message', handler);
|
|
122
144
|
return () => window.removeEventListener('message', handler);
|
|
123
|
-
}, [syncUrl, isDocsTab]);
|
|
145
|
+
}, [currentUrl, syncUrl, isDocsTab]);
|
|
124
146
|
|
|
125
147
|
useEffect(() => {
|
|
126
148
|
if (!currentTab || !customRenderer?.onIframeMessage) {
|
|
@@ -130,12 +152,13 @@ export function DocBrowser({
|
|
|
130
152
|
customRenderer.onIframeMessage?.({
|
|
131
153
|
event,
|
|
132
154
|
iframe: iframeRef.current,
|
|
155
|
+
iframeInstanceId,
|
|
133
156
|
tab: currentTab,
|
|
134
157
|
});
|
|
135
158
|
};
|
|
136
159
|
window.addEventListener('message', handler);
|
|
137
160
|
return () => window.removeEventListener('message', handler);
|
|
138
|
-
}, [currentTab, customRenderer]);
|
|
161
|
+
}, [currentTab, customRenderer, iframeInstanceId]);
|
|
139
162
|
|
|
140
163
|
const startFloatDrag = useCallback((event: React.PointerEvent<HTMLElement>) => {
|
|
141
164
|
event.preventDefault();
|
|
@@ -236,36 +259,34 @@ export function DocBrowser({
|
|
|
236
259
|
tab: currentTab,
|
|
237
260
|
} : undefined;
|
|
238
261
|
const customToolbar = customRenderParams ? customRenderer?.renderToolbar?.(customRenderParams) : null;
|
|
239
|
-
const customContent = customRenderParams
|
|
262
|
+
const customContent = customRenderParams
|
|
263
|
+
? customRenderer?.renderContent?.(customRenderParams) ?? (isHomeTab ? <DocBrowserHomePage open={open} /> : null)
|
|
264
|
+
: null;
|
|
240
265
|
const iframeSandbox = currentTab
|
|
241
266
|
? customRenderer?.getIframeSandbox?.(currentTab) ?? 'allow-same-origin allow-scripts allow-popups allow-forms'
|
|
242
267
|
: 'allow-same-origin allow-scripts allow-popups allow-forms';
|
|
243
268
|
|
|
244
269
|
const panelContent = (
|
|
245
270
|
<>
|
|
246
|
-
<DocBrowserPanelHeader
|
|
247
|
-
currentTab={currentTab}
|
|
248
|
-
customRenderer={customRenderer}
|
|
249
|
-
isDocked={isDocked}
|
|
250
|
-
isFullscreen={isFullscreen}
|
|
251
|
-
onClose={close}
|
|
252
|
-
onDragStart={startFloatDrag}
|
|
253
|
-
onToggleMode={toggleMode}
|
|
254
|
-
/>
|
|
255
|
-
|
|
256
271
|
<DocBrowserTabStrip
|
|
257
272
|
tabs={tabs}
|
|
258
273
|
activeTabId={activeTabId}
|
|
259
|
-
|
|
274
|
+
canGoBack={canGoBack}
|
|
275
|
+
canGoForward={canGoForward}
|
|
276
|
+
isDocked={isDocked}
|
|
277
|
+
isFullscreen={isFullscreen}
|
|
278
|
+
onGoBack={goBack}
|
|
279
|
+
onGoForward={goForward}
|
|
280
|
+
onOpenNewTab={openNewTab}
|
|
260
281
|
onSetActiveTab={setActiveTab}
|
|
261
282
|
onCloseTab={closeTab}
|
|
283
|
+
onClose={close}
|
|
284
|
+
onDragStart={startFloatDrag}
|
|
285
|
+
onToggleMode={toggleMode}
|
|
262
286
|
/>
|
|
263
287
|
|
|
264
288
|
<DocBrowserDocsToolbar
|
|
265
|
-
currentTab={currentTab}
|
|
266
289
|
isDocsTab={isDocsTab}
|
|
267
|
-
onBack={goBack}
|
|
268
|
-
onForward={goForward}
|
|
269
290
|
onSubmit={handleUrlSubmit}
|
|
270
291
|
onUrlInputChange={setUrlInput}
|
|
271
292
|
urlInput={urlInput}
|
|
@@ -274,16 +295,14 @@ export function DocBrowser({
|
|
|
274
295
|
{customToolbar}
|
|
275
296
|
|
|
276
297
|
<DocBrowserFrameContent
|
|
277
|
-
activeTabId={activeTabId}
|
|
278
298
|
currentTab={currentTab}
|
|
279
299
|
currentUrl={currentUrl}
|
|
280
300
|
customContent={customContent}
|
|
281
301
|
iframeRef={iframeRef}
|
|
282
|
-
|
|
302
|
+
iframeInstanceId={iframeInstanceId}
|
|
283
303
|
iframeSandbox={iframeSandbox}
|
|
284
304
|
isDragging={floatInteraction?.kind === 'drag'}
|
|
285
305
|
isResizing={floatInteraction?.kind === 'resize'}
|
|
286
|
-
navVersion={navVersion}
|
|
287
306
|
/>
|
|
288
307
|
|
|
289
308
|
<DocBrowserExternalLink currentUrl={currentUrl} isDocsTab={isDocsTab} />
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type {
|
|
2
|
+
DocBrowserActiveHistoryEntry,
|
|
2
3
|
DocBrowserOpenOptions,
|
|
3
4
|
DocBrowserState,
|
|
4
5
|
DocBrowserStateUpdate,
|
|
@@ -6,15 +7,17 @@ import type {
|
|
|
6
7
|
DocBrowserTabKind,
|
|
7
8
|
} from '@/shared/components/doc-browser/types/doc-browser.types';
|
|
8
9
|
import {
|
|
10
|
+
createDocBrowserActiveHistoryEntry,
|
|
9
11
|
createDefaultDocBrowserState,
|
|
10
12
|
createDocBrowserTab,
|
|
11
13
|
} from '@/shared/components/doc-browser/utils/doc-browser-state.utils';
|
|
12
14
|
import {
|
|
15
|
+
DOC_BROWSER_HOME_TAB_KIND,
|
|
13
16
|
getDefaultDocsUrl,
|
|
14
|
-
inferTabKind,
|
|
15
|
-
normalizeDocUrl,
|
|
16
|
-
normalizeUrlByKind,
|
|
17
17
|
} from '@/shared/components/doc-browser/utils/doc-browser-url.utils';
|
|
18
|
+
import {
|
|
19
|
+
docBrowserRouteRegistry,
|
|
20
|
+
} from '@/shared/components/doc-browser/utils/doc-browser-route-registry.utils';
|
|
18
21
|
import { useDocBrowserStore } from '@/shared/components/doc-browser/stores/doc-browser.store';
|
|
19
22
|
|
|
20
23
|
function updateTab(
|
|
@@ -32,61 +35,103 @@ function updateActiveTab(state: DocBrowserState, updater: (tab: DocBrowserTab) =
|
|
|
32
35
|
return updateTab(state, state.activeTabId, updater);
|
|
33
36
|
}
|
|
34
37
|
|
|
35
|
-
function
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
38
|
+
function appendManualNavigation(tab: DocBrowserTab, url: string): Pick<DocBrowserTab, 'history' | 'historyIndex'> {
|
|
39
|
+
const history = [...tab.history.slice(0, tab.historyIndex + 1), url];
|
|
40
|
+
return {
|
|
41
|
+
history,
|
|
42
|
+
historyIndex: history.length - 1,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function areActiveHistoryEntriesEquivalent(
|
|
47
|
+
current: DocBrowserActiveHistoryEntry,
|
|
48
|
+
next: DocBrowserActiveHistoryEntry,
|
|
40
49
|
): boolean {
|
|
41
|
-
|
|
42
|
-
|
|
50
|
+
return current.tabId === next.tabId
|
|
51
|
+
&& current.kind === next.kind
|
|
52
|
+
&& docBrowserRouteRegistry.areUrlsEquivalent(current.url, next.url, current.kind, next.kind);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function pushActiveHistory(state: DocBrowserState, entry: DocBrowserActiveHistoryEntry): DocBrowserState {
|
|
56
|
+
const current = state.activeHistory[state.activeHistoryIndex];
|
|
57
|
+
if (current && areActiveHistoryEntriesEquivalent(current, entry)) {
|
|
58
|
+
return state;
|
|
59
|
+
}
|
|
60
|
+
const activeHistory = [...state.activeHistory.slice(0, state.activeHistoryIndex + 1), entry];
|
|
61
|
+
return {
|
|
62
|
+
...state,
|
|
63
|
+
activeHistory,
|
|
64
|
+
activeHistoryIndex: activeHistory.length - 1,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function findTabHistoryIndex(tab: DocBrowserTab, url: string): number {
|
|
69
|
+
for (let index = tab.history.length - 1; index >= 0; index -= 1) {
|
|
70
|
+
if (docBrowserRouteRegistry.areUrlsEquivalent(tab.history[index], url, tab.kind, tab.kind)) {
|
|
71
|
+
return index;
|
|
72
|
+
}
|
|
43
73
|
}
|
|
44
|
-
return
|
|
74
|
+
return -1;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function restoreActiveHistoryEntry(state: DocBrowserState, entry: DocBrowserActiveHistoryEntry): DocBrowserState {
|
|
78
|
+
const tab = state.tabs.find((item) => item.id === entry.tabId);
|
|
79
|
+
if (!tab) {
|
|
80
|
+
return state;
|
|
81
|
+
}
|
|
82
|
+
const target = docBrowserRouteRegistry.resolveOpenTarget({
|
|
83
|
+
activeTab: tab,
|
|
84
|
+
kind: entry.kind,
|
|
85
|
+
url: entry.url,
|
|
86
|
+
});
|
|
87
|
+
const historyIndex = findTabHistoryIndex(tab, target.url);
|
|
88
|
+
return updateTab(
|
|
89
|
+
{
|
|
90
|
+
...state,
|
|
91
|
+
activeTabId: tab.id,
|
|
92
|
+
isOpen: true,
|
|
93
|
+
},
|
|
94
|
+
tab.id,
|
|
95
|
+
(currentTab) => ({
|
|
96
|
+
...currentTab,
|
|
97
|
+
currentUrl: target.url,
|
|
98
|
+
dedupeKey: target.dedupeKey,
|
|
99
|
+
historyIndex: historyIndex >= 0 ? historyIndex : currentTab.historyIndex,
|
|
100
|
+
kind: target.kind,
|
|
101
|
+
title: target.title,
|
|
102
|
+
}),
|
|
103
|
+
);
|
|
45
104
|
}
|
|
46
105
|
|
|
47
106
|
function updateTabForOpen(
|
|
48
107
|
tab: DocBrowserTab,
|
|
49
|
-
|
|
50
|
-
|
|
108
|
+
target: {
|
|
109
|
+
kind: DocBrowserTabKind;
|
|
110
|
+
title: string;
|
|
111
|
+
url: string;
|
|
112
|
+
},
|
|
51
113
|
options?: DocBrowserOpenOptions,
|
|
52
114
|
dedupeKey?: string,
|
|
53
115
|
): DocBrowserTab {
|
|
54
116
|
const baseTab = {
|
|
55
117
|
...tab,
|
|
56
|
-
title: options?.title || tab.title,
|
|
57
|
-
kind,
|
|
118
|
+
title: options?.title || target.title || tab.title,
|
|
119
|
+
kind: target.kind,
|
|
58
120
|
dedupeKey,
|
|
59
121
|
};
|
|
60
122
|
|
|
61
|
-
if (
|
|
123
|
+
if (docBrowserRouteRegistry.areUrlsEquivalent(tab.currentUrl, target.url, tab.kind, target.kind)) {
|
|
62
124
|
return baseTab;
|
|
63
125
|
}
|
|
64
126
|
|
|
65
127
|
return {
|
|
66
128
|
...baseTab,
|
|
67
|
-
currentUrl: url,
|
|
68
|
-
|
|
69
|
-
historyIndex: tab.historyIndex + 1,
|
|
129
|
+
currentUrl: target.url,
|
|
130
|
+
...appendManualNavigation(tab, target.url),
|
|
70
131
|
navVersion: tab.navVersion + 1,
|
|
71
132
|
};
|
|
72
133
|
}
|
|
73
134
|
|
|
74
|
-
function resolveOpenTargetUrl(params: {
|
|
75
|
-
url?: string;
|
|
76
|
-
kind: DocBrowserTabKind;
|
|
77
|
-
activeTab?: DocBrowserTab;
|
|
78
|
-
}): string {
|
|
79
|
-
const { activeTab, kind, url } = params;
|
|
80
|
-
if (url && url.trim().length > 0) {
|
|
81
|
-
return normalizeUrlByKind(url, kind);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
if (kind === 'docs') {
|
|
85
|
-
return getDefaultDocsUrl();
|
|
86
|
-
}
|
|
87
|
-
return activeTab?.currentUrl ?? getDefaultDocsUrl();
|
|
88
|
-
}
|
|
89
|
-
|
|
90
135
|
function openDocBrowserState(
|
|
91
136
|
prev: DocBrowserState,
|
|
92
137
|
url?: string,
|
|
@@ -100,9 +145,12 @@ function openDocBrowserState(
|
|
|
100
145
|
title,
|
|
101
146
|
} = options ?? {};
|
|
102
147
|
const activeTab = prev.tabs.find((tab) => tab.id === prev.activeTabId) ?? prev.tabs[0];
|
|
103
|
-
const
|
|
104
|
-
|
|
105
|
-
|
|
148
|
+
const target = docBrowserRouteRegistry.resolveOpenTarget({
|
|
149
|
+
activeTab,
|
|
150
|
+
kind,
|
|
151
|
+
url,
|
|
152
|
+
});
|
|
153
|
+
const dedupeKey = rawDedupeKey?.trim() || target.dedupeKey;
|
|
106
154
|
const matchedTab = dedupeKey
|
|
107
155
|
? prev.tabs.find((tab) => tab.dedupeKey === dedupeKey)
|
|
108
156
|
: undefined;
|
|
@@ -111,30 +159,40 @@ function openDocBrowserState(
|
|
|
111
159
|
const next = updateTab(
|
|
112
160
|
prev,
|
|
113
161
|
matchedTab.id,
|
|
114
|
-
(tab) => updateTabForOpen(tab,
|
|
162
|
+
(tab) => updateTabForOpen(tab, target, options, dedupeKey),
|
|
115
163
|
);
|
|
116
|
-
|
|
164
|
+
const shouldActivate = activate !== false;
|
|
165
|
+
const activeTabId = shouldActivate ? matchedTab.id : prev.activeTabId;
|
|
166
|
+
const nextState = {
|
|
117
167
|
...next,
|
|
118
|
-
activeTabId
|
|
168
|
+
activeTabId,
|
|
119
169
|
isOpen: true,
|
|
120
170
|
};
|
|
171
|
+
const shouldPushActiveHistory = shouldActivate || matchedTab.id === prev.activeTabId;
|
|
172
|
+
return shouldPushActiveHistory
|
|
173
|
+
? pushActiveHistory(nextState, { kind: target.kind, tabId: matchedTab.id, url: target.url })
|
|
174
|
+
: nextState;
|
|
121
175
|
}
|
|
122
176
|
|
|
123
|
-
if (shouldForceNewTab || dedupeKey || !activeTab || activeTab.kind !==
|
|
124
|
-
const newTab = createDocBrowserTab(
|
|
125
|
-
|
|
177
|
+
if (shouldForceNewTab || dedupeKey || !activeTab || activeTab.kind !== target.kind) {
|
|
178
|
+
const newTab = createDocBrowserTab(target.url, target.kind, title ?? target.title, dedupeKey);
|
|
179
|
+
const nextState = {
|
|
126
180
|
...prev,
|
|
127
181
|
isOpen: true,
|
|
128
182
|
tabs: [...prev.tabs, newTab],
|
|
129
183
|
activeTabId: newTab.id,
|
|
130
184
|
};
|
|
185
|
+
return pushActiveHistory(nextState, createDocBrowserActiveHistoryEntry(newTab));
|
|
131
186
|
}
|
|
132
187
|
|
|
133
188
|
const next = updateActiveTab(
|
|
134
189
|
prev,
|
|
135
|
-
(tab) => updateTabForOpen(tab,
|
|
190
|
+
(tab) => updateTabForOpen(tab, target, options, dedupeKey),
|
|
191
|
+
);
|
|
192
|
+
return pushActiveHistory(
|
|
193
|
+
{ ...next, isOpen: true },
|
|
194
|
+
{ kind: target.kind, tabId: prev.activeTabId, url: target.url },
|
|
136
195
|
);
|
|
137
|
-
return { ...next, isOpen: true };
|
|
138
196
|
}
|
|
139
197
|
|
|
140
198
|
export class DocBrowserManager {
|
|
@@ -146,6 +204,10 @@ export class DocBrowserManager {
|
|
|
146
204
|
this.setSnapshot((prev) => openDocBrowserState(prev, url, options));
|
|
147
205
|
};
|
|
148
206
|
|
|
207
|
+
readonly openNewTab = (): void => {
|
|
208
|
+
this.open(undefined, { kind: DOC_BROWSER_HOME_TAB_KIND, newTab: true });
|
|
209
|
+
};
|
|
210
|
+
|
|
149
211
|
readonly close = (): void => {
|
|
150
212
|
this.setSnapshot((prev) => ({ ...prev, isOpen: false }));
|
|
151
213
|
};
|
|
@@ -166,24 +228,30 @@ export class DocBrowserManager {
|
|
|
166
228
|
};
|
|
167
229
|
}
|
|
168
230
|
|
|
169
|
-
|
|
170
|
-
if (tab
|
|
231
|
+
const next = updateActiveTab(prev, (tab) => {
|
|
232
|
+
if (!docBrowserRouteRegistry.usesManagedHistory(tab)) {
|
|
171
233
|
return tab;
|
|
172
234
|
}
|
|
235
|
+
const target = docBrowserRouteRegistry.resolveOpenTarget({ activeTab: tab, url });
|
|
173
236
|
|
|
174
|
-
|
|
175
|
-
if (normalizeDocUrl(targetUrl) === normalizeDocUrl(tab.currentUrl)) {
|
|
237
|
+
if (docBrowserRouteRegistry.areUrlsEquivalent(tab.currentUrl, target.url, tab.kind, target.kind)) {
|
|
176
238
|
return tab;
|
|
177
239
|
}
|
|
178
240
|
|
|
179
241
|
return {
|
|
180
242
|
...tab,
|
|
181
|
-
currentUrl:
|
|
182
|
-
|
|
183
|
-
|
|
243
|
+
currentUrl: target.url,
|
|
244
|
+
dedupeKey: target.dedupeKey,
|
|
245
|
+
...appendManualNavigation(tab, target.url),
|
|
246
|
+
kind: target.kind,
|
|
184
247
|
navVersion: tab.navVersion + 1,
|
|
248
|
+
title: target.title,
|
|
185
249
|
};
|
|
186
250
|
});
|
|
251
|
+
const currentTab = next.tabs.find((tab) => tab.id === next.activeTabId);
|
|
252
|
+
return currentTab
|
|
253
|
+
? pushActiveHistory(next, createDocBrowserActiveHistoryEntry(currentTab))
|
|
254
|
+
: next;
|
|
187
255
|
});
|
|
188
256
|
};
|
|
189
257
|
|
|
@@ -198,39 +266,58 @@ export class DocBrowserManager {
|
|
|
198
266
|
};
|
|
199
267
|
}
|
|
200
268
|
|
|
201
|
-
|
|
202
|
-
if (tab
|
|
269
|
+
const next = updateActiveTab(prev, (tab) => {
|
|
270
|
+
if (!docBrowserRouteRegistry.usesManagedHistory(tab)) {
|
|
203
271
|
return tab;
|
|
204
272
|
}
|
|
273
|
+
const target = docBrowserRouteRegistry.resolveOpenTarget({ activeTab: tab, kind: tab.kind, url });
|
|
205
274
|
|
|
206
|
-
if (
|
|
275
|
+
if (docBrowserRouteRegistry.areUrlsEquivalent(tab.currentUrl, target.url, tab.kind, target.kind)) {
|
|
207
276
|
return tab;
|
|
208
277
|
}
|
|
209
278
|
|
|
210
279
|
return {
|
|
211
280
|
...tab,
|
|
212
|
-
currentUrl: url,
|
|
213
|
-
|
|
214
|
-
|
|
281
|
+
currentUrl: target.url,
|
|
282
|
+
dedupeKey: target.dedupeKey,
|
|
283
|
+
...appendManualNavigation(tab, target.url),
|
|
284
|
+
kind: target.kind,
|
|
285
|
+
title: target.title,
|
|
215
286
|
};
|
|
216
287
|
});
|
|
288
|
+
const currentTab = next.tabs.find((tab) => tab.id === next.activeTabId);
|
|
289
|
+
return currentTab
|
|
290
|
+
? pushActiveHistory(next, createDocBrowserActiveHistoryEntry(currentTab))
|
|
291
|
+
: next;
|
|
217
292
|
});
|
|
218
293
|
};
|
|
219
294
|
|
|
220
295
|
readonly goBack = (): void => {
|
|
221
|
-
this.setSnapshot((prev) =>
|
|
222
|
-
if (
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
296
|
+
this.setSnapshot((prev) => {
|
|
297
|
+
if (prev.activeHistoryIndex <= 0) {
|
|
298
|
+
return prev;
|
|
299
|
+
}
|
|
300
|
+
const activeHistoryIndex = prev.activeHistoryIndex - 1;
|
|
301
|
+
const entry = prev.activeHistory[activeHistoryIndex];
|
|
302
|
+
if (!entry) {
|
|
303
|
+
return prev;
|
|
304
|
+
}
|
|
305
|
+
return restoreActiveHistoryEntry({ ...prev, activeHistoryIndex }, entry);
|
|
306
|
+
});
|
|
226
307
|
};
|
|
227
308
|
|
|
228
309
|
readonly goForward = (): void => {
|
|
229
|
-
this.setSnapshot((prev) =>
|
|
230
|
-
if (
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
310
|
+
this.setSnapshot((prev) => {
|
|
311
|
+
if (prev.activeHistoryIndex >= prev.activeHistory.length - 1) {
|
|
312
|
+
return prev;
|
|
313
|
+
}
|
|
314
|
+
const activeHistoryIndex = prev.activeHistoryIndex + 1;
|
|
315
|
+
const entry = prev.activeHistory[activeHistoryIndex];
|
|
316
|
+
if (!entry) {
|
|
317
|
+
return prev;
|
|
318
|
+
}
|
|
319
|
+
return restoreActiveHistoryEntry({ ...prev, activeHistoryIndex }, entry);
|
|
320
|
+
});
|
|
234
321
|
};
|
|
235
322
|
|
|
236
323
|
readonly closeTab = (tabId: string): void => {
|
|
@@ -253,11 +340,29 @@ export class DocBrowserManager {
|
|
|
253
340
|
? nextTabs[Math.max(0, index - 1)]?.id ?? nextTabs[0].id
|
|
254
341
|
: prev.activeTabId;
|
|
255
342
|
|
|
256
|
-
|
|
343
|
+
const nextState = {
|
|
257
344
|
...prev,
|
|
258
345
|
tabs: nextTabs,
|
|
259
346
|
activeTabId: nextActiveId,
|
|
260
347
|
};
|
|
348
|
+
const activeHistory = nextState.activeHistory.filter((entry) => entry.tabId !== tabId);
|
|
349
|
+
const activeHistoryBeforeIndex = nextState.activeHistory
|
|
350
|
+
.slice(0, nextState.activeHistoryIndex + 1)
|
|
351
|
+
.filter((entry) => entry.tabId !== tabId);
|
|
352
|
+
const nextActiveTab = nextTabs.find((tab) => tab.id === nextActiveId) ?? nextTabs[0];
|
|
353
|
+
const reconciledState = {
|
|
354
|
+
...nextState,
|
|
355
|
+
activeHistory: activeHistory.length > 0
|
|
356
|
+
? activeHistory
|
|
357
|
+
: [createDocBrowserActiveHistoryEntry(nextActiveTab)],
|
|
358
|
+
activeHistoryIndex: Math.min(
|
|
359
|
+
Math.max(0, activeHistoryBeforeIndex.length - 1),
|
|
360
|
+
Math.max(0, activeHistory.length - 1),
|
|
361
|
+
),
|
|
362
|
+
};
|
|
363
|
+
return prev.activeTabId === tabId
|
|
364
|
+
? pushActiveHistory(reconciledState, createDocBrowserActiveHistoryEntry(nextActiveTab))
|
|
365
|
+
: reconciledState;
|
|
261
366
|
});
|
|
262
367
|
};
|
|
263
368
|
|
|
@@ -266,7 +371,14 @@ export class DocBrowserManager {
|
|
|
266
371
|
if (!prev.tabs.some((tab) => tab.id === tabId)) {
|
|
267
372
|
return prev;
|
|
268
373
|
}
|
|
269
|
-
|
|
374
|
+
const tab = prev.tabs.find((item) => item.id === tabId);
|
|
375
|
+
if (!tab) {
|
|
376
|
+
return prev;
|
|
377
|
+
}
|
|
378
|
+
return pushActiveHistory(
|
|
379
|
+
{ ...prev, activeTabId: tabId, isOpen: true },
|
|
380
|
+
createDocBrowserActiveHistoryEntry(tab),
|
|
381
|
+
);
|
|
270
382
|
});
|
|
271
383
|
};
|
|
272
384
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { create } from 'zustand';
|
|
2
2
|
import { createJSONStorage, persist } from 'zustand/middleware';
|
|
3
3
|
import type {
|
|
4
|
+
DocBrowserActiveHistoryEntry,
|
|
4
5
|
DocBrowserMode,
|
|
5
6
|
DocBrowserState,
|
|
6
7
|
DocBrowserStateUpdate,
|
|
@@ -71,6 +72,36 @@ function normalizePersistedDocBrowserTab(value: unknown): DocBrowserTab | null {
|
|
|
71
72
|
};
|
|
72
73
|
}
|
|
73
74
|
|
|
75
|
+
function createActiveHistoryEntryFromTab(tab: DocBrowserTab): DocBrowserActiveHistoryEntry {
|
|
76
|
+
return {
|
|
77
|
+
kind: tab.kind,
|
|
78
|
+
tabId: tab.id,
|
|
79
|
+
url: tab.currentUrl,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function normalizePersistedActiveHistoryEntry(
|
|
84
|
+
value: unknown,
|
|
85
|
+
tabsById: Map<string, DocBrowserTab>,
|
|
86
|
+
): DocBrowserActiveHistoryEntry | null {
|
|
87
|
+
if (
|
|
88
|
+
!isRecord(value)
|
|
89
|
+
|| typeof value.tabId !== 'string'
|
|
90
|
+
|| typeof value.kind !== 'string'
|
|
91
|
+
|| typeof value.url !== 'string'
|
|
92
|
+
|| value.url.trim().length === 0
|
|
93
|
+
|| !tabsById.has(value.tabId)
|
|
94
|
+
) {
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return {
|
|
99
|
+
kind: value.kind,
|
|
100
|
+
tabId: value.tabId,
|
|
101
|
+
url: value.url,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
74
105
|
function normalizePersistedDocBrowserState(value: unknown): DocBrowserState | null {
|
|
75
106
|
if (!isRecord(value)) {
|
|
76
107
|
return null;
|
|
@@ -88,12 +119,30 @@ function normalizePersistedDocBrowserState(value: unknown): DocBrowserState | nu
|
|
|
88
119
|
const activeTabId = tabs.some((tab) => tab.id === persistedActiveTabId)
|
|
89
120
|
? persistedActiveTabId ?? tabs[0].id
|
|
90
121
|
: tabs[0].id;
|
|
122
|
+
const tabsById = new Map(tabs.map((tab) => [tab.id, tab]));
|
|
123
|
+
const activeHistory = Array.isArray(value.activeHistory)
|
|
124
|
+
? value.activeHistory
|
|
125
|
+
.map((entry) => normalizePersistedActiveHistoryEntry(entry, tabsById))
|
|
126
|
+
.filter((entry): entry is DocBrowserActiveHistoryEntry => entry !== null)
|
|
127
|
+
: [];
|
|
128
|
+
const fallbackHistory = [createActiveHistoryEntryFromTab(tabsById.get(activeTabId) ?? tabs[0])];
|
|
129
|
+
const resolvedActiveHistory = activeHistory.length > 0 ? activeHistory : fallbackHistory;
|
|
130
|
+
const activeHistoryIndex = normalizePersistedNumber(
|
|
131
|
+
value.activeHistoryIndex,
|
|
132
|
+
0,
|
|
133
|
+
resolvedActiveHistory.length - 1,
|
|
134
|
+
resolvedActiveHistory.length - 1,
|
|
135
|
+
);
|
|
136
|
+
const activeHistoryEntry = resolvedActiveHistory[activeHistoryIndex];
|
|
137
|
+
const resolvedActiveTabId = tabsById.has(activeHistoryEntry.tabId) ? activeHistoryEntry.tabId : activeTabId;
|
|
91
138
|
|
|
92
139
|
return {
|
|
93
140
|
isOpen: value.isOpen === true,
|
|
94
141
|
mode: isDocBrowserMode(value.mode) ? value.mode : 'docked',
|
|
95
142
|
tabs,
|
|
96
|
-
activeTabId,
|
|
143
|
+
activeTabId: resolvedActiveTabId,
|
|
144
|
+
activeHistory: resolvedActiveHistory,
|
|
145
|
+
activeHistoryIndex,
|
|
97
146
|
};
|
|
98
147
|
}
|
|
99
148
|
|
|
@@ -122,6 +171,8 @@ export const useDocBrowserStore = create<DocBrowserStore>()(
|
|
|
122
171
|
mode: state.snapshot.mode,
|
|
123
172
|
tabs: state.snapshot.tabs.slice(-DOC_BROWSER_MAX_PERSISTED_TABS),
|
|
124
173
|
activeTabId: state.snapshot.activeTabId,
|
|
174
|
+
activeHistory: state.snapshot.activeHistory,
|
|
175
|
+
activeHistoryIndex: state.snapshot.activeHistoryIndex,
|
|
125
176
|
},
|
|
126
177
|
}),
|
|
127
178
|
merge: (persistedState, currentState) => {
|