@qilin-agent/client-ui-sidebar-browser 0.0.0-stage → 3.1.4
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/LICENSE +21 -0
- package/README.i18n.yaml +52 -0
- package/README.md +114 -2
- package/README.zh.md +115 -0
- package/lib/client.js +834 -0
- package/lib/index.js +6 -0
- package/lib/types/client/browser/BrowserController.d.ts +76 -0
- package/lib/types/client/browser/BrowserFrame.d.ts +60 -0
- package/lib/types/client/browser/BrowserNavigation.d.ts +109 -0
- package/lib/types/client/browser/store.d.ts +21 -0
- package/lib/types/client/browser/url.d.ts +31 -0
- package/lib/types/client/definition.d.ts +14 -0
- package/lib/types/client/index.d.ts +22 -0
- package/lib/types/client/locales.d.ts +59 -0
- package/lib/types/client/view/BrowserBody.d.ts +15 -0
- package/lib/types/client/view/BrowserTitle.d.ts +13 -0
- package/lib/types/index.d.ts +4 -0
- package/package.json +66 -3
package/lib/client.js
ADDED
|
@@ -0,0 +1,834 @@
|
|
|
1
|
+
window.__ModuleLoader__.load({
|
|
2
|
+
id: "@qilin-agent/client-ui-sidebar-browser",
|
|
3
|
+
factory: (require) => {
|
|
4
|
+
var module = { exports: {} };
|
|
5
|
+
var exports = module.exports;
|
|
6
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
7
|
+
let react_jsx_runtime = require("react/jsx-runtime");
|
|
8
|
+
let react = require("react");
|
|
9
|
+
let _qilin_agent_client_ui_primitives = require("@qilin-agent/client-ui-primitives");
|
|
10
|
+
let _qilin_agent_client_store = require("@qilin-agent/client-store");
|
|
11
|
+
/**
|
|
12
|
+
* Owns the application-known URL history and the iframe observation state machine.
|
|
13
|
+
* The first load for a request keeps its URL authoritative; another load marks it unknown.
|
|
14
|
+
*/
|
|
15
|
+
var BrowserNavigation = class BrowserNavigation {
|
|
16
|
+
value;
|
|
17
|
+
/**
|
|
18
|
+
* @param initial - persisted state restored for this tab, or a fresh empty state.
|
|
19
|
+
*/
|
|
20
|
+
constructor(initial = BrowserNavigation.empty()) {
|
|
21
|
+
this.value = initial;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Create state before a tab has a controlled navigation target.
|
|
25
|
+
* @returns empty serializable state.
|
|
26
|
+
*/
|
|
27
|
+
static empty() {
|
|
28
|
+
return {
|
|
29
|
+
entries: [],
|
|
30
|
+
index: -1,
|
|
31
|
+
request: void 0,
|
|
32
|
+
navigation: { status: "empty" },
|
|
33
|
+
failure: void 0
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Read the selected application-history entry.
|
|
38
|
+
* @param state - serializable tab state.
|
|
39
|
+
* @returns the current target, if any.
|
|
40
|
+
*/
|
|
41
|
+
static current(state) {
|
|
42
|
+
return state === void 0 || state.index < 0 ? void 0 : state.entries[state.index];
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Test whether the Web carrier can use the preceding application-history entry.
|
|
46
|
+
* @param state - serializable tab state.
|
|
47
|
+
* @returns whether Back is available.
|
|
48
|
+
*/
|
|
49
|
+
static canGoBack(state) {
|
|
50
|
+
return state.navigation.status !== "unknown" && state.index > 0;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Test whether the Web carrier can use the following application-history entry.
|
|
54
|
+
* @param state - serializable tab state.
|
|
55
|
+
* @returns whether Forward is available.
|
|
56
|
+
*/
|
|
57
|
+
static canGoForward(state) {
|
|
58
|
+
return state.navigation.status !== "unknown" && state.index >= 0 && state.index < state.entries.length - 1;
|
|
59
|
+
}
|
|
60
|
+
/** Current immutable serializable state. */
|
|
61
|
+
get snapshot() {
|
|
62
|
+
return this.value;
|
|
63
|
+
}
|
|
64
|
+
/** Whether the Web iframe can safely use the application-owned Back entry. */
|
|
65
|
+
get canGoBack() {
|
|
66
|
+
return BrowserNavigation.canGoBack(this.value);
|
|
67
|
+
}
|
|
68
|
+
/** Whether the Web iframe can safely use the application-owned Forward entry. */
|
|
69
|
+
get canGoForward() {
|
|
70
|
+
return BrowserNavigation.canGoForward(this.value);
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Add a controlled target and discard its stale forward branch.
|
|
74
|
+
* @param target - validated canonical target.
|
|
75
|
+
* @returns the new load request.
|
|
76
|
+
*/
|
|
77
|
+
navigate(target) {
|
|
78
|
+
const entries = [...this.value.entries.slice(0, this.value.index + 1), target];
|
|
79
|
+
if (entries.length > 100) entries.splice(0, entries.length - 100);
|
|
80
|
+
return this.request(target, {
|
|
81
|
+
...this.value,
|
|
82
|
+
entries,
|
|
83
|
+
index: entries.length - 1
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Select the preceding application-known target.
|
|
88
|
+
* @returns a new load request, or undefined when unavailable.
|
|
89
|
+
*/
|
|
90
|
+
back() {
|
|
91
|
+
if (!this.canGoBack) return void 0;
|
|
92
|
+
const index = this.value.index - 1;
|
|
93
|
+
const target = this.value.entries[index];
|
|
94
|
+
return this.request(target, {
|
|
95
|
+
...this.value,
|
|
96
|
+
index
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Select the following application-known target.
|
|
101
|
+
* @returns a new load request, or undefined when unavailable.
|
|
102
|
+
*/
|
|
103
|
+
forward() {
|
|
104
|
+
if (!this.canGoForward) return void 0;
|
|
105
|
+
const index = this.value.index + 1;
|
|
106
|
+
const target = this.value.entries[index];
|
|
107
|
+
return this.request(target, {
|
|
108
|
+
...this.value,
|
|
109
|
+
index
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Start another load of the last application-known target.
|
|
114
|
+
* @returns a new load request, or undefined before the first target.
|
|
115
|
+
*/
|
|
116
|
+
reload() {
|
|
117
|
+
const target = BrowserNavigation.current(this.value);
|
|
118
|
+
return target === void 0 ? void 0 : this.request(target, this.value);
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Record an invalid address without changing the active document state.
|
|
122
|
+
* @param reason - parser refusal.
|
|
123
|
+
*/
|
|
124
|
+
addressFailed(reason) {
|
|
125
|
+
this.value = {
|
|
126
|
+
...this.value,
|
|
127
|
+
failure: {
|
|
128
|
+
kind: "address",
|
|
129
|
+
reason
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Record a frame load for its captured revision.
|
|
135
|
+
* @param revision - revision bound to the rendered frame.
|
|
136
|
+
*/
|
|
137
|
+
frameLoaded(revision) {
|
|
138
|
+
const navigation = this.value.navigation;
|
|
139
|
+
if (navigation.status === "empty" || navigation.revision !== revision) return;
|
|
140
|
+
if (navigation.status === "loading") this.value = {
|
|
141
|
+
...this.value,
|
|
142
|
+
navigation: {
|
|
143
|
+
status: "known",
|
|
144
|
+
revision
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
else if (navigation.status === "known") this.value = {
|
|
148
|
+
...this.value,
|
|
149
|
+
navigation: {
|
|
150
|
+
status: "unknown",
|
|
151
|
+
revision
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
request(target, basis) {
|
|
156
|
+
const request = {
|
|
157
|
+
revision: (this.value.request?.revision ?? 0) + 1,
|
|
158
|
+
target
|
|
159
|
+
};
|
|
160
|
+
this.value = {
|
|
161
|
+
...basis,
|
|
162
|
+
request,
|
|
163
|
+
navigation: {
|
|
164
|
+
status: "loading",
|
|
165
|
+
revision: request.revision
|
|
166
|
+
},
|
|
167
|
+
failure: void 0
|
|
168
|
+
};
|
|
169
|
+
return request;
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
//#endregion
|
|
173
|
+
//#region \0qilin-css:/home/runner/work/QiLin/QiLin/packages/client/ui-sidebar-browser/src/client/view/Browser.module.css.mjs
|
|
174
|
+
const css = ".Ll_csG_root{height:100%;min-height:0;color:var(--qilin-alias-label-primary);background:var(--qilin-alias-bg-base);flex-direction:column;flex:auto;display:flex}.Ll_csG_toolbar{box-sizing:border-box;border-bottom:.5px solid var(--qilin-alias-border-l3);flex:none;align-items:center;gap:4px;height:38px;padding:5px 6px;display:flex}.Ll_csG_tool{width:28px;height:28px;color:var(--qilin-alias-label-secondary);cursor:pointer;background:0 0;border:0;border-radius:6px;flex:none;justify-content:center;align-items:center;padding:0;display:inline-flex}.Ll_csG_tool:hover:not(:disabled){color:var(--qilin-alias-label-primary);background:var(--qilin-alias-interactive-bg-hover)}.Ll_csG_tool:disabled{color:var(--qilin-alias-label-quaternary);cursor:default}.Ll_csG_sandboxOff{color:var(--qilin-alias-state-error-primary);background:color-mix(in srgb, var(--qilin-alias-state-error-primary) 10%, transparent)}.Ll_csG_addressBox{flex:auto;min-width:0;position:relative}.Ll_csG_address{box-sizing:border-box;width:100%;height:28px;color:var(--qilin-alias-label-primary);font:var(--qilin-font-xxs-12);background:var(--qilin-alias-bg-layer-1);border:.5px solid var(--qilin-alias-border-l2);border-radius:6px;padding:0 34px 0 9px}.Ll_csG_addressGo{visibility:hidden;opacity:0;position:absolute;top:0;right:0}.Ll_csG_addressBox:focus-within .Ll_csG_addressGo{visibility:visible;opacity:1}.Ll_csG_addressUnknown{color:var(--qilin-alias-label-tertiary);background:var(--qilin-alias-bg-layer-2)}.Ll_csG_addressUnknown:focus{color:var(--qilin-alias-label-primary);background:var(--qilin-alias-bg-layer-1)}.Ll_csG_addressChanged{color:var(--qilin-alias-label-tertiary);font:var(--qilin-font-xxxs-11);pointer-events:none;background:var(--qilin-alias-bg-layer-2);padding-left:8px;line-height:16px;position:absolute;top:6px;right:8px}.Ll_csG_addressBox:focus-within .Ll_csG_addressChanged{visibility:hidden;opacity:0}.Ll_csG_address:focus{outline:1px solid var(--qilin-alias-state-business-primary);outline-offset:-1px}.Ll_csG_frame{background:var(--qilin-alias-bg-base);border:0;flex:auto;width:100%;min-height:0;display:flex}.Ll_csG_start{min-height:0;color:var(--qilin-alias-label-tertiary);font:var(--qilin-font-xs-13);text-align:center;flex:auto;justify-content:center;align-items:center;padding:24px;display:flex}.Ll_csG_failure{color:var(--qilin-alias-state-error-primary);font:var(--qilin-font-xxxs-11);background:color-mix(in srgb, var(--qilin-alias-state-error-primary) 8%, transparent);flex:none;padding:6px 12px}.Ll_csG_sandboxWarning{color:var(--qilin-alias-state-warning-primary,var(--qilin-alias-state-business-primary));font:var(--qilin-font-xxxs-11);background:color-mix(in srgb, var(--qilin-alias-state-warning-primary,var(--qilin-alias-state-business-primary)) 8%, transparent);flex:none;padding:6px 12px}.Ll_csG_limit{color:var(--qilin-alias-label-tertiary);font:var(--qilin-font-xxxs-11);text-overflow:ellipsis;white-space:nowrap;border-top:.5px solid var(--qilin-alias-border-l3);flex:none;margin:0;padding:4px 10px;overflow:hidden}.Ll_csG_titleIcon{flex:none;margin-right:4px}";
|
|
175
|
+
const tagId = "@qilin-agent/client-ui-sidebar-browser/Browser.module.css";
|
|
176
|
+
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
|
|
177
|
+
const tag = document.createElement("style");
|
|
178
|
+
tag.dataset.plugin = "@qilin-agent/client-ui-sidebar-browser";
|
|
179
|
+
tag.dataset.pluginCss = tagId;
|
|
180
|
+
tag.textContent = css;
|
|
181
|
+
document.head.appendChild(tag);
|
|
182
|
+
}
|
|
183
|
+
var Browser_module_css_default = {
|
|
184
|
+
"address": "Ll_csG_address",
|
|
185
|
+
"addressBox": "Ll_csG_addressBox",
|
|
186
|
+
"addressChanged": "Ll_csG_addressChanged",
|
|
187
|
+
"addressGo": "Ll_csG_addressGo",
|
|
188
|
+
"addressUnknown": "Ll_csG_addressUnknown",
|
|
189
|
+
"failure": "Ll_csG_failure",
|
|
190
|
+
"frame": "Ll_csG_frame",
|
|
191
|
+
"limit": "Ll_csG_limit",
|
|
192
|
+
"root": "Ll_csG_root",
|
|
193
|
+
"sandboxOff": "Ll_csG_sandboxOff",
|
|
194
|
+
"sandboxWarning": "Ll_csG_sandboxWarning",
|
|
195
|
+
"start": "Ll_csG_start",
|
|
196
|
+
"titleIcon": "Ll_csG_titleIcon",
|
|
197
|
+
"tool": "Ll_csG_tool",
|
|
198
|
+
"toolbar": "Ll_csG_toolbar"
|
|
199
|
+
};
|
|
200
|
+
//#endregion
|
|
201
|
+
//#region lib/types/client/view/BrowserBody.js
|
|
202
|
+
/** Browser toolbar and Web iframe renderer. */
|
|
203
|
+
/** Fixed Web iframe sandbox; popups escape the sandbox while top navigation remains absent. */
|
|
204
|
+
const WEB_BROWSER_SANDBOX = "allow-scripts allow-forms allow-same-origin allow-popups allow-popups-to-escape-sandbox";
|
|
205
|
+
const INITIAL_BROWSER_FRAME = {
|
|
206
|
+
document: void 0,
|
|
207
|
+
sandboxed: true,
|
|
208
|
+
loadFailed: false
|
|
209
|
+
};
|
|
210
|
+
function SandboxPolicyIcon({ sandboxed }) {
|
|
211
|
+
return (0, react_jsx_runtime.jsxs)("svg", {
|
|
212
|
+
width: "15",
|
|
213
|
+
height: "15",
|
|
214
|
+
viewBox: "0 0 16 16",
|
|
215
|
+
fill: "none",
|
|
216
|
+
"aria-hidden": true,
|
|
217
|
+
children: [(0, react_jsx_runtime.jsx)("path", {
|
|
218
|
+
d: _qilin_agent_client_ui_primitives.SHIELD_OUTLINE_PATH,
|
|
219
|
+
stroke: "currentColor",
|
|
220
|
+
strokeWidth: _qilin_agent_client_ui_primitives.SHIELD_OUTLINE_STROKE,
|
|
221
|
+
strokeLinejoin: "round"
|
|
222
|
+
}), sandboxed ? (0, react_jsx_runtime.jsx)("path", {
|
|
223
|
+
d: "M12.1654 5.7552L8.9447 9.41475C8.73044 9.65816 8.53628 9.8804 8.35774 10.0423C8.1713 10.2114 7.94235 10.3717 7.64016 10.4254C7.48207 10.4535 7.32 10.4552 7.16151 10.4294C6.85843 10.3801 6.62728 10.2223 6.43836 10.0559C6.25752 9.89653 6.06037 9.67732 5.84264 9.43705L4.72925 8.20897L5.63557 7.38707L6.74897 8.61594C6.98603 8.87755 7.12974 9.03533 7.24673 9.13839C7.31033 9.19443 7.34485 9.21476 7.35823 9.22122C7.38068 9.22484 7.40352 9.22515 7.42593 9.22122C7.40522 9.22502 7.42893 9.23294 7.53583 9.136C7.65132 9.03126 7.79316 8.87139 8.02643 8.60638L11.2479 4.94763L12.1654 5.7552Z",
|
|
224
|
+
fill: "currentColor"
|
|
225
|
+
}) : (0, react_jsx_runtime.jsx)("path", {
|
|
226
|
+
d: "M10.6074 4.40278L8.00975 6.99973L10.6074 9.59739L9.59736 10.6074L6.9997 8.00978L4.40274 10.6074L3.3927 9.59739L5.98966 6.99973L3.3927 4.40278L4.40274 3.39273L6.9997 5.98969L9.59736 3.39273L10.6074 4.40278Z",
|
|
227
|
+
fill: "currentColor",
|
|
228
|
+
transform: "translate(1.2 0.8)"
|
|
229
|
+
})]
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
/** Translate one parser refusal without matching display strings in logic. */
|
|
233
|
+
function failureText(reason, t) {
|
|
234
|
+
return t(`error.${reason}`);
|
|
235
|
+
}
|
|
236
|
+
function useBrowserDraft(controlledUrl, requestId) {
|
|
237
|
+
const [edit, setEdit] = (0, react.useState)();
|
|
238
|
+
return [edit !== void 0 && edit.requestId === requestId ? edit.value : controlledUrl ?? "", (draft) => {
|
|
239
|
+
setEdit({
|
|
240
|
+
requestId,
|
|
241
|
+
value: draft
|
|
242
|
+
});
|
|
243
|
+
}];
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Browser tab renderer for a controller-owned URL state and Web iframe carrier.
|
|
247
|
+
* @param props - composed slot props for the Browser tab body.
|
|
248
|
+
* @returns the toolbar and the carrier frame.
|
|
249
|
+
*/
|
|
250
|
+
function BrowserBody(props) {
|
|
251
|
+
const { goBack, goForward, loadUrl, mount, reload, reportLoaded, reportLoadFailed, toggleSandbox, useBrowserFrame, useStore, useTabInfo, t } = props;
|
|
252
|
+
const { tab } = useTabInfo();
|
|
253
|
+
const state = useStore((snapshot) => snapshot.byTab[tab.id]) ?? BrowserNavigation.empty();
|
|
254
|
+
const initialState = (0, react.useRef)(state);
|
|
255
|
+
const initialUrl = (0, react.useRef)(tab.navigation.params?.url);
|
|
256
|
+
const current = BrowserNavigation.current(state);
|
|
257
|
+
const [draft, setDraft] = useBrowserDraft(current?.url ?? initialUrl.current, state.request?.revision);
|
|
258
|
+
const [mountCount, setMountCount] = (0, react.useState)(0);
|
|
259
|
+
(0, react.useEffect)(() => {
|
|
260
|
+
mount(tab.id, tab.signal, window.location.origin, initialState.current);
|
|
261
|
+
setMountCount((count) => count + 1);
|
|
262
|
+
}, [
|
|
263
|
+
mount,
|
|
264
|
+
tab.id,
|
|
265
|
+
tab.signal
|
|
266
|
+
]);
|
|
267
|
+
(0, react.useEffect)(() => {
|
|
268
|
+
if (mountCount === 0) return;
|
|
269
|
+
if (BrowserNavigation.current(initialState.current) !== void 0) {
|
|
270
|
+
reload(tab.id);
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
const url = initialUrl.current;
|
|
274
|
+
if (url !== void 0) loadUrl(tab.id, url);
|
|
275
|
+
}, [
|
|
276
|
+
loadUrl,
|
|
277
|
+
mountCount,
|
|
278
|
+
reload,
|
|
279
|
+
tab.id
|
|
280
|
+
]);
|
|
281
|
+
const { document, sandboxed, loadFailed } = useBrowserFrame(tab.id) ?? INITIAL_BROWSER_FRAME;
|
|
282
|
+
const navigationUnknown = state.navigation.status === "unknown";
|
|
283
|
+
const externalUrl = navigationUnknown ? void 0 : current?.url;
|
|
284
|
+
const submit = (event) => {
|
|
285
|
+
event.preventDefault();
|
|
286
|
+
loadUrl(tab.id, draft);
|
|
287
|
+
};
|
|
288
|
+
const failure = state.failure === void 0 ? void 0 : failureText(state.failure.reason, t);
|
|
289
|
+
const placeholder = current === void 0 ? t("start") : t("loading");
|
|
290
|
+
return (0, react_jsx_runtime.jsxs)("div", {
|
|
291
|
+
className: Browser_module_css_default.root,
|
|
292
|
+
children: [
|
|
293
|
+
(0, react_jsx_runtime.jsxs)("form", {
|
|
294
|
+
className: Browser_module_css_default.toolbar,
|
|
295
|
+
onSubmit: submit,
|
|
296
|
+
children: [
|
|
297
|
+
(0, react_jsx_runtime.jsx)("button", {
|
|
298
|
+
type: "button",
|
|
299
|
+
className: Browser_module_css_default.tool,
|
|
300
|
+
"aria-label": t("back"),
|
|
301
|
+
title: t("back"),
|
|
302
|
+
disabled: !BrowserNavigation.canGoBack(state),
|
|
303
|
+
onClick: () => {
|
|
304
|
+
goBack(tab.id);
|
|
305
|
+
},
|
|
306
|
+
children: (0, react_jsx_runtime.jsx)(_qilin_agent_client_ui_primitives.IconChevronLeftOutline14, {})
|
|
307
|
+
}),
|
|
308
|
+
(0, react_jsx_runtime.jsx)("button", {
|
|
309
|
+
type: "button",
|
|
310
|
+
className: Browser_module_css_default.tool,
|
|
311
|
+
"aria-label": t("forward"),
|
|
312
|
+
title: t("forward"),
|
|
313
|
+
disabled: !BrowserNavigation.canGoForward(state),
|
|
314
|
+
onClick: () => {
|
|
315
|
+
goForward(tab.id);
|
|
316
|
+
},
|
|
317
|
+
children: (0, react_jsx_runtime.jsx)(_qilin_agent_client_ui_primitives.IconChevronRightOutline14, {})
|
|
318
|
+
}),
|
|
319
|
+
(0, react_jsx_runtime.jsx)("button", {
|
|
320
|
+
type: "button",
|
|
321
|
+
className: Browser_module_css_default.tool,
|
|
322
|
+
"aria-label": t("reload"),
|
|
323
|
+
title: t("reload"),
|
|
324
|
+
disabled: current === void 0,
|
|
325
|
+
onClick: () => {
|
|
326
|
+
reload(tab.id);
|
|
327
|
+
},
|
|
328
|
+
children: (0, react_jsx_runtime.jsx)(_qilin_agent_client_ui_primitives.IconRefreshOutline14, {})
|
|
329
|
+
}),
|
|
330
|
+
(0, react_jsx_runtime.jsxs)("div", {
|
|
331
|
+
className: Browser_module_css_default.addressBox,
|
|
332
|
+
children: [
|
|
333
|
+
(0, react_jsx_runtime.jsx)("input", {
|
|
334
|
+
className: `${Browser_module_css_default.address} ${navigationUnknown ? Browser_module_css_default.addressUnknown : ""}`,
|
|
335
|
+
value: draft,
|
|
336
|
+
"aria-label": t("address.placeholder"),
|
|
337
|
+
placeholder: t("address.placeholder"),
|
|
338
|
+
spellCheck: false,
|
|
339
|
+
onChange: (event) => {
|
|
340
|
+
setDraft(event.currentTarget.value);
|
|
341
|
+
}
|
|
342
|
+
}),
|
|
343
|
+
navigationUnknown && (0, react_jsx_runtime.jsx)("span", {
|
|
344
|
+
className: Browser_module_css_default.addressChanged,
|
|
345
|
+
children: t("address.changed")
|
|
346
|
+
}),
|
|
347
|
+
(0, react_jsx_runtime.jsx)("button", {
|
|
348
|
+
type: "submit",
|
|
349
|
+
className: `${Browser_module_css_default.tool} ${Browser_module_css_default.addressGo}`,
|
|
350
|
+
"aria-label": t("go"),
|
|
351
|
+
title: t("go"),
|
|
352
|
+
children: (0, react_jsx_runtime.jsx)(_qilin_agent_client_ui_primitives.IconLinkOutline14, {})
|
|
353
|
+
})
|
|
354
|
+
]
|
|
355
|
+
}),
|
|
356
|
+
(0, react_jsx_runtime.jsx)("button", {
|
|
357
|
+
type: "button",
|
|
358
|
+
className: Browser_module_css_default.tool,
|
|
359
|
+
"aria-label": t("external"),
|
|
360
|
+
title: t("external"),
|
|
361
|
+
disabled: externalUrl === void 0,
|
|
362
|
+
onClick: () => {
|
|
363
|
+
/* v8 ignore next -- React does not dispatch clicks from this disabled button. */
|
|
364
|
+
if (externalUrl !== void 0) window.open(externalUrl, "_blank", "noopener,noreferrer");
|
|
365
|
+
},
|
|
366
|
+
children: (0, react_jsx_runtime.jsx)(_qilin_agent_client_ui_primitives.IconRightUpOutline16, { size: 14 })
|
|
367
|
+
}),
|
|
368
|
+
(0, react_jsx_runtime.jsx)("button", {
|
|
369
|
+
type: "button",
|
|
370
|
+
className: `${Browser_module_css_default.tool} ${sandboxed ? "" : Browser_module_css_default.sandboxOff}`,
|
|
371
|
+
"aria-label": t(sandboxed ? "sandbox.disable" : "sandbox.enable"),
|
|
372
|
+
title: t(sandboxed ? "sandbox.disable" : "sandbox.enable"),
|
|
373
|
+
"aria-pressed": !sandboxed,
|
|
374
|
+
disabled: mountCount === 0,
|
|
375
|
+
onClick: () => {
|
|
376
|
+
toggleSandbox(tab.id);
|
|
377
|
+
},
|
|
378
|
+
children: (0, react_jsx_runtime.jsx)(SandboxPolicyIcon, { sandboxed })
|
|
379
|
+
})
|
|
380
|
+
]
|
|
381
|
+
}),
|
|
382
|
+
!sandboxed && (0, react_jsx_runtime.jsx)("div", {
|
|
383
|
+
className: Browser_module_css_default.sandboxWarning,
|
|
384
|
+
role: "status",
|
|
385
|
+
children: t("sandbox.warning")
|
|
386
|
+
}),
|
|
387
|
+
loadFailed && (0, react_jsx_runtime.jsx)("div", {
|
|
388
|
+
className: Browser_module_css_default.failure,
|
|
389
|
+
role: "status",
|
|
390
|
+
children: t("web.loadFailed")
|
|
391
|
+
}),
|
|
392
|
+
failure !== void 0 && (0, react_jsx_runtime.jsx)("div", {
|
|
393
|
+
className: Browser_module_css_default.failure,
|
|
394
|
+
role: "alert",
|
|
395
|
+
children: failure
|
|
396
|
+
}),
|
|
397
|
+
document === void 0 ? (0, react_jsx_runtime.jsx)("div", {
|
|
398
|
+
className: Browser_module_css_default.start,
|
|
399
|
+
children: placeholder
|
|
400
|
+
}) : (0, react_jsx_runtime.jsx)("iframe", {
|
|
401
|
+
className: Browser_module_css_default.frame,
|
|
402
|
+
src: document.src,
|
|
403
|
+
sandbox: sandboxed ? WEB_BROWSER_SANDBOX : void 0,
|
|
404
|
+
referrerPolicy: "no-referrer",
|
|
405
|
+
title: document.target.title,
|
|
406
|
+
onLoad: () => {
|
|
407
|
+
reportLoaded(tab.id, document.revision);
|
|
408
|
+
},
|
|
409
|
+
/* v8 ignore next -- jsdom does not dispatch React iframe error events; BrowserFrame owns the tested behavior. */
|
|
410
|
+
onError: () => {
|
|
411
|
+
reportLoadFailed(tab.id, document.revision);
|
|
412
|
+
},
|
|
413
|
+
"data-sidebar-browser-frame": true
|
|
414
|
+
}, `${document.target.url}:${String(document.revision)}`),
|
|
415
|
+
navigationUnknown && (0, react_jsx_runtime.jsx)("p", {
|
|
416
|
+
className: Browser_module_css_default.limit,
|
|
417
|
+
children: t("web.unknown")
|
|
418
|
+
})
|
|
419
|
+
]
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
//#endregion
|
|
423
|
+
//#region lib/types/client/view/BrowserTitle.js
|
|
424
|
+
/**
|
|
425
|
+
* Browser icon and current host name.
|
|
426
|
+
* @param props - composed slot props for the Browser tab title.
|
|
427
|
+
* @returns the icon and the current host name.
|
|
428
|
+
*/
|
|
429
|
+
function BrowserTitle(props) {
|
|
430
|
+
const { useTabInfo, useStore } = props;
|
|
431
|
+
const { tab } = useTabInfo();
|
|
432
|
+
const entry = useStore((state) => BrowserNavigation.current(state.byTab[tab.id]));
|
|
433
|
+
return (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(0, react_jsx_runtime.jsx)(_qilin_agent_client_ui_primitives.IconGlobeOutline14, { className: Browser_module_css_default.titleIcon }), entry?.title ?? tab.title] });
|
|
434
|
+
}
|
|
435
|
+
//#endregion
|
|
436
|
+
//#region lib/types/client/browser/BrowserFrame.js
|
|
437
|
+
/** BrowserFrame interface and the current iframe implementation. */
|
|
438
|
+
/** Owns transient iframe and sandbox-toggle state independently from URL navigation. */
|
|
439
|
+
var IframeImpl = class {
|
|
440
|
+
sandboxChanged;
|
|
441
|
+
documentLoaded;
|
|
442
|
+
store = (0, _qilin_agent_client_store.createSnapshotStore)({
|
|
443
|
+
document: void 0,
|
|
444
|
+
sandboxed: true,
|
|
445
|
+
loadFailed: false
|
|
446
|
+
});
|
|
447
|
+
/**
|
|
448
|
+
* @param sandboxChanged - applies the new policy to the current controlled target.
|
|
449
|
+
* @param documentLoaded - reports an iframe load to the navigation state machine.
|
|
450
|
+
*/
|
|
451
|
+
constructor(sandboxChanged, documentLoaded) {
|
|
452
|
+
this.sandboxChanged = sandboxChanged;
|
|
453
|
+
this.documentLoaded = documentLoaded;
|
|
454
|
+
}
|
|
455
|
+
/** @returns the immutable renderer snapshot. */
|
|
456
|
+
getSnapshot = () => this.store.getSnapshot();
|
|
457
|
+
/**
|
|
458
|
+
* Subscribe to iframe or sandbox-mode changes.
|
|
459
|
+
* @param listener - invalidation callback.
|
|
460
|
+
* @returns the unsubscribe function.
|
|
461
|
+
*/
|
|
462
|
+
subscribe = (listener) => this.store.subscribe(listener);
|
|
463
|
+
/** Toggle sandbox enforcement for this tab occurrence. */
|
|
464
|
+
toggleSandbox() {
|
|
465
|
+
const current = this.store.getSnapshot();
|
|
466
|
+
const sandboxed = !current.sandboxed;
|
|
467
|
+
this.store.set({
|
|
468
|
+
...current,
|
|
469
|
+
sandboxed
|
|
470
|
+
});
|
|
471
|
+
this.sandboxChanged(sandboxed);
|
|
472
|
+
}
|
|
473
|
+
/**
|
|
474
|
+
* Publish a prepared frame from the owning controller.
|
|
475
|
+
* @param document - current prepared document.
|
|
476
|
+
*/
|
|
477
|
+
setDocument(document) {
|
|
478
|
+
this.store.set({
|
|
479
|
+
...this.store.getSnapshot(),
|
|
480
|
+
document,
|
|
481
|
+
loadFailed: false
|
|
482
|
+
});
|
|
483
|
+
}
|
|
484
|
+
/** Remove the current prepared frame and transient load failure. */
|
|
485
|
+
clearDocument() {
|
|
486
|
+
const current = this.store.getSnapshot();
|
|
487
|
+
const { document } = current;
|
|
488
|
+
if (document !== void 0) this.store.set({
|
|
489
|
+
...current,
|
|
490
|
+
document: void 0,
|
|
491
|
+
loadFailed: false
|
|
492
|
+
});
|
|
493
|
+
}
|
|
494
|
+
/** @param revision - rendered document revision reported by the iframe. */
|
|
495
|
+
reportLoaded(revision) {
|
|
496
|
+
this.documentLoaded(revision);
|
|
497
|
+
}
|
|
498
|
+
/** @param revision - rendered document revision whose iframe emitted `error`. */
|
|
499
|
+
reportLoadFailed(revision) {
|
|
500
|
+
const current = this.store.getSnapshot();
|
|
501
|
+
if (current.document?.revision !== revision || current.loadFailed) return;
|
|
502
|
+
this.store.set({
|
|
503
|
+
...current,
|
|
504
|
+
loadFailed: true
|
|
505
|
+
});
|
|
506
|
+
}
|
|
507
|
+
};
|
|
508
|
+
/**
|
|
509
|
+
* Parse one address-bar value into the fixed protocol allowlist.
|
|
510
|
+
* @param input - user or typed-open input.
|
|
511
|
+
* @param applicationOrigin - current QILIN document origin, blocked for HTTPS.
|
|
512
|
+
* @returns a canonical target or the refusal reason.
|
|
513
|
+
*/
|
|
514
|
+
function parseBrowserAddress(input, applicationOrigin) {
|
|
515
|
+
const trimmed = input.trim();
|
|
516
|
+
if (trimmed === "") return {
|
|
517
|
+
ok: false,
|
|
518
|
+
reason: "empty"
|
|
519
|
+
};
|
|
520
|
+
if (trimmed.length > 16384) return {
|
|
521
|
+
ok: false,
|
|
522
|
+
reason: "invalid"
|
|
523
|
+
};
|
|
524
|
+
const candidate = /^[A-Za-z][A-Za-z\d+.-]*:(?!\d+(?:[/?#]|$))/u.test(trimmed) ? trimmed : `https://${trimmed}`;
|
|
525
|
+
let url;
|
|
526
|
+
try {
|
|
527
|
+
url = new URL(candidate);
|
|
528
|
+
} catch {
|
|
529
|
+
return {
|
|
530
|
+
ok: false,
|
|
531
|
+
reason: "invalid"
|
|
532
|
+
};
|
|
533
|
+
}
|
|
534
|
+
if (url.username !== "" || url.password !== "") return {
|
|
535
|
+
ok: false,
|
|
536
|
+
reason: "credentials"
|
|
537
|
+
};
|
|
538
|
+
if (url.protocol === "https:" || url.protocol === "http:") {
|
|
539
|
+
if (applicationOrigin !== void 0 && applicationOrigin !== "null") try {
|
|
540
|
+
if (url.origin === new URL(applicationOrigin).origin) return {
|
|
541
|
+
ok: false,
|
|
542
|
+
reason: "application-origin"
|
|
543
|
+
};
|
|
544
|
+
} catch {}
|
|
545
|
+
return {
|
|
546
|
+
ok: true,
|
|
547
|
+
target: {
|
|
548
|
+
kind: url.protocol === "https:" ? "https" : "http",
|
|
549
|
+
url: url.href,
|
|
550
|
+
title: url.hostname
|
|
551
|
+
}
|
|
552
|
+
};
|
|
553
|
+
}
|
|
554
|
+
return {
|
|
555
|
+
ok: false,
|
|
556
|
+
reason: "protocol"
|
|
557
|
+
};
|
|
558
|
+
}
|
|
559
|
+
//#endregion
|
|
560
|
+
//#region lib/types/client/browser/BrowserController.js
|
|
561
|
+
/** Owns one Browser tab's URL state, loading lifecycle, and four navigation commands. */
|
|
562
|
+
var BrowserController = class {
|
|
563
|
+
options;
|
|
564
|
+
/** Renderer-facing iframe and sandbox state. */
|
|
565
|
+
frame;
|
|
566
|
+
navigation;
|
|
567
|
+
disposed = false;
|
|
568
|
+
/**
|
|
569
|
+
* @param options - tab identity, persistence writer, URL origin, and lifetime.
|
|
570
|
+
*/
|
|
571
|
+
constructor(options) {
|
|
572
|
+
this.options = options;
|
|
573
|
+
this.navigation = new BrowserNavigation(options.initial);
|
|
574
|
+
this.frame = new IframeImpl(() => {
|
|
575
|
+
this.reload();
|
|
576
|
+
}, (revision) => {
|
|
577
|
+
this.frameLoaded(revision);
|
|
578
|
+
});
|
|
579
|
+
options.signal.addEventListener("abort", () => {
|
|
580
|
+
this.dispose();
|
|
581
|
+
}, { once: true });
|
|
582
|
+
}
|
|
583
|
+
/**
|
|
584
|
+
* Validate and load one address, replacing a same-address load with Reload.
|
|
585
|
+
* @param value - address-bar or typed-tab value.
|
|
586
|
+
*/
|
|
587
|
+
loadUrl(value) {
|
|
588
|
+
if (this.disposed) return;
|
|
589
|
+
const parsed = parseBrowserAddress(value, this.options.applicationOrigin);
|
|
590
|
+
if (!parsed.ok) {
|
|
591
|
+
this.navigation.addressFailed(parsed.reason);
|
|
592
|
+
this.publish();
|
|
593
|
+
return;
|
|
594
|
+
}
|
|
595
|
+
if (BrowserNavigation.current(this.navigation.snapshot)?.url === parsed.target.url) {
|
|
596
|
+
this.reload();
|
|
597
|
+
return;
|
|
598
|
+
}
|
|
599
|
+
this.start(this.navigation.navigate(parsed.target));
|
|
600
|
+
}
|
|
601
|
+
/** Move to the preceding application-known address when Web history remains usable. */
|
|
602
|
+
goBack() {
|
|
603
|
+
if (this.disposed) return;
|
|
604
|
+
const request = this.navigation.back();
|
|
605
|
+
if (request !== void 0) this.start(request);
|
|
606
|
+
}
|
|
607
|
+
/** Move to the following application-known address when Web history remains usable. */
|
|
608
|
+
goForward() {
|
|
609
|
+
if (this.disposed) return;
|
|
610
|
+
const request = this.navigation.forward();
|
|
611
|
+
if (request !== void 0) this.start(request);
|
|
612
|
+
}
|
|
613
|
+
/** Reload the last application-known address without adding history. */
|
|
614
|
+
reload() {
|
|
615
|
+
if (this.disposed) return;
|
|
616
|
+
const request = this.navigation.reload();
|
|
617
|
+
if (request !== void 0) this.start(request);
|
|
618
|
+
}
|
|
619
|
+
start(request) {
|
|
620
|
+
this.publish();
|
|
621
|
+
this.frame.clearDocument();
|
|
622
|
+
this.frame.setDocument({
|
|
623
|
+
target: request.target,
|
|
624
|
+
src: request.target.url,
|
|
625
|
+
revision: request.revision
|
|
626
|
+
});
|
|
627
|
+
}
|
|
628
|
+
frameLoaded(revision) {
|
|
629
|
+
if (this.disposed) return;
|
|
630
|
+
const previous = this.navigation.snapshot;
|
|
631
|
+
this.navigation.frameLoaded(revision);
|
|
632
|
+
if (this.navigation.snapshot !== previous) this.publish();
|
|
633
|
+
}
|
|
634
|
+
publish() {
|
|
635
|
+
this.options.actions.replace(this.options.tabId, this.navigation.snapshot);
|
|
636
|
+
}
|
|
637
|
+
dispose() {
|
|
638
|
+
this.disposed = true;
|
|
639
|
+
this.frame.clearDocument();
|
|
640
|
+
}
|
|
641
|
+
};
|
|
642
|
+
/**
|
|
643
|
+
* Bind Browser controllers to one Session and its persistence writer.
|
|
644
|
+
* @param actions - Browser store mutation face.
|
|
645
|
+
* @returns a per-tab controller registry exposed as plain Slot callbacks.
|
|
646
|
+
*/
|
|
647
|
+
function createBrowserControllers(actions) {
|
|
648
|
+
const controllers = /* @__PURE__ */ new Map();
|
|
649
|
+
const controller = (tabId) => controllers.get(tabId)?.controller;
|
|
650
|
+
return {
|
|
651
|
+
keyedHooks: { browserFrame: (key) => controller(key)?.frame },
|
|
652
|
+
mount(tabId, signal, applicationOrigin, initial) {
|
|
653
|
+
if (controllers.get(tabId)?.signal === signal) return;
|
|
654
|
+
const created = new BrowserController({
|
|
655
|
+
tabId,
|
|
656
|
+
signal,
|
|
657
|
+
applicationOrigin,
|
|
658
|
+
actions,
|
|
659
|
+
...initial === void 0 ? {} : { initial }
|
|
660
|
+
});
|
|
661
|
+
controllers.set(tabId, {
|
|
662
|
+
signal,
|
|
663
|
+
controller: created
|
|
664
|
+
});
|
|
665
|
+
signal.addEventListener("abort", () => {
|
|
666
|
+
if (controllers.get(tabId)?.controller !== created) return;
|
|
667
|
+
controllers.delete(tabId);
|
|
668
|
+
actions.forget(tabId);
|
|
669
|
+
}, { once: true });
|
|
670
|
+
},
|
|
671
|
+
loadUrl: (tabId, value) => {
|
|
672
|
+
controller(tabId)?.loadUrl(value);
|
|
673
|
+
},
|
|
674
|
+
goBack: (tabId) => {
|
|
675
|
+
controller(tabId)?.goBack();
|
|
676
|
+
},
|
|
677
|
+
goForward: (tabId) => {
|
|
678
|
+
controller(tabId)?.goForward();
|
|
679
|
+
},
|
|
680
|
+
reload: (tabId) => {
|
|
681
|
+
controller(tabId)?.reload();
|
|
682
|
+
},
|
|
683
|
+
toggleSandbox: (tabId) => {
|
|
684
|
+
controller(tabId)?.frame.toggleSandbox();
|
|
685
|
+
},
|
|
686
|
+
reportLoaded: (tabId, revision) => {
|
|
687
|
+
controller(tabId)?.frame.reportLoaded(revision);
|
|
688
|
+
},
|
|
689
|
+
reportLoadFailed: (tabId, revision) => {
|
|
690
|
+
controller(tabId)?.frame.reportLoadFailed(revision);
|
|
691
|
+
}
|
|
692
|
+
};
|
|
693
|
+
}
|
|
694
|
+
//#endregion
|
|
695
|
+
//#region lib/types/client/definition.js
|
|
696
|
+
/** Browser tab kind. */
|
|
697
|
+
const BROWSER_KIND = "browser";
|
|
698
|
+
/** Browser implementation identity and keyed Slot dispatch key. */
|
|
699
|
+
const BROWSER_ID = "@qilin-agent/client-ui-sidebar-browser";
|
|
700
|
+
/**
|
|
701
|
+
* Build the Browser type with locale-live copy.
|
|
702
|
+
* @param t - namespace-bound translate, read fresh on every label call.
|
|
703
|
+
* @returns the definition to register.
|
|
704
|
+
*/
|
|
705
|
+
function browserDefinition(t) {
|
|
706
|
+
return {
|
|
707
|
+
id: BROWSER_ID,
|
|
708
|
+
kind: BROWSER_KIND,
|
|
709
|
+
multiple: true,
|
|
710
|
+
priority: "builtin",
|
|
711
|
+
title: () => t("type.label"),
|
|
712
|
+
guide: [{
|
|
713
|
+
id: "new",
|
|
714
|
+
order: 30,
|
|
715
|
+
title: () => t("guide.title"),
|
|
716
|
+
description: () => t("guide.description"),
|
|
717
|
+
icon: _qilin_agent_client_ui_primitives.IconGlobeOutline14
|
|
718
|
+
}]
|
|
719
|
+
};
|
|
720
|
+
}
|
|
721
|
+
//#endregion
|
|
722
|
+
//#region lib/types/client/locales.js
|
|
723
|
+
/** Locale-owned Browser tab copy. */
|
|
724
|
+
const zh = {
|
|
725
|
+
"type.label": "浏览器",
|
|
726
|
+
"guide.title": "浏览器",
|
|
727
|
+
"guide.description": "浏览 HTTP(S) 网页",
|
|
728
|
+
"address.placeholder": "输入 HTTP(S) 地址",
|
|
729
|
+
"address.changed": "URL 已变化",
|
|
730
|
+
back: "后退",
|
|
731
|
+
forward: "前进",
|
|
732
|
+
reload: "刷新",
|
|
733
|
+
go: "前往",
|
|
734
|
+
external: "在系统浏览器中打开",
|
|
735
|
+
"sandbox.disable": "关闭沙箱限制",
|
|
736
|
+
"sandbox.enable": "恢复沙箱限制",
|
|
737
|
+
"sandbox.warning": "沙箱限制已关闭;页面可以导航顶层应用,并使用下载、模态对话框与输入锁定。",
|
|
738
|
+
start: "输入 HTTP(S) 地址开始浏览",
|
|
739
|
+
loading: "正在打开…",
|
|
740
|
+
"error.empty": "请输入地址。",
|
|
741
|
+
"error.invalid": "这个地址无效或过长。",
|
|
742
|
+
"error.protocol": "只支持 HTTP 和 HTTPS 地址;本地文件请使用文档预览。",
|
|
743
|
+
"error.credentials": "地址不能包含用户名或密码。",
|
|
744
|
+
"error.application-origin": "不能在嵌入浏览器中打开 QILIN 应用自身。",
|
|
745
|
+
"web.loadFailed": "页面报告加载失败或可能禁止嵌入;可尝试在系统浏览器中打开。",
|
|
746
|
+
"web.unknown": "页面已在 iframe 内跳转;Web 模式无法读取当前 URL。"
|
|
747
|
+
};
|
|
748
|
+
/** English dictionary with the same keys. */
|
|
749
|
+
const en = {
|
|
750
|
+
"type.label": "Browser",
|
|
751
|
+
"guide.title": "Browser",
|
|
752
|
+
"guide.description": "Browse HTTP(S) pages",
|
|
753
|
+
"address.placeholder": "Enter an HTTP(S) address",
|
|
754
|
+
"address.changed": "URL changed",
|
|
755
|
+
back: "Back",
|
|
756
|
+
forward: "Forward",
|
|
757
|
+
reload: "Reload",
|
|
758
|
+
go: "Go",
|
|
759
|
+
external: "Open in system browser",
|
|
760
|
+
"sandbox.disable": "Disable sandbox restrictions",
|
|
761
|
+
"sandbox.enable": "Restore sandbox restrictions",
|
|
762
|
+
"sandbox.warning": "Sandbox restrictions are disabled; the page can navigate the top-level app and use downloads, modal dialogs, and input locks.",
|
|
763
|
+
start: "Enter an HTTP(S) address to start browsing",
|
|
764
|
+
loading: "Opening…",
|
|
765
|
+
"error.empty": "Enter an address.",
|
|
766
|
+
"error.invalid": "That address is invalid or too long.",
|
|
767
|
+
"error.protocol": "Only HTTP and HTTPS addresses are supported; use Document Preview for local files.",
|
|
768
|
+
"error.credentials": "Addresses cannot contain a username or password.",
|
|
769
|
+
"error.application-origin": "The embedded browser cannot open the QILIN application itself.",
|
|
770
|
+
"web.loadFailed": "The page reported a load failure or may block embedding; try opening it in the system browser.",
|
|
771
|
+
"web.unknown": "The page navigated inside the iframe; Web mode cannot read its current URL."
|
|
772
|
+
};
|
|
773
|
+
//#endregion
|
|
774
|
+
//#region lib/types/client/browser/store.js
|
|
775
|
+
/** Persisted Browser tab snapshots shared by the body and title slots. */
|
|
776
|
+
/**
|
|
777
|
+
* Declare the Session-scoped Browser persistence store.
|
|
778
|
+
* @returns a fresh store handle for Slot registration.
|
|
779
|
+
*/
|
|
780
|
+
function createBrowserStore() {
|
|
781
|
+
return (0, _qilin_agent_client_store.defineStore)({
|
|
782
|
+
init: () => ({ byTab: {} }),
|
|
783
|
+
persist: "qilin.sidebar-browser.v1",
|
|
784
|
+
actions: {
|
|
785
|
+
replace: (draft, tabId, state) => {
|
|
786
|
+
draft.byTab[tabId] = state;
|
|
787
|
+
},
|
|
788
|
+
forget: (draft, tabId) => {
|
|
789
|
+
const byTab = {};
|
|
790
|
+
for (const [id, state] of Object.entries(draft.byTab)) if (id !== tabId) byTab[id] = state;
|
|
791
|
+
draft.byTab = byTab;
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
});
|
|
795
|
+
}
|
|
796
|
+
//#endregion
|
|
797
|
+
//#region lib/types/client/index.js
|
|
798
|
+
/** Required Browser services. */
|
|
799
|
+
const inject = [
|
|
800
|
+
"slots",
|
|
801
|
+
"locale",
|
|
802
|
+
"sidebarRightTabs"
|
|
803
|
+
];
|
|
804
|
+
/** Register the Browser type, localized guide entry, body, and title. */
|
|
805
|
+
function apply(ctx) {
|
|
806
|
+
const namespace = "sidebarBrowser";
|
|
807
|
+
const t = ctx.locale.bind(namespace);
|
|
808
|
+
const store = createBrowserStore();
|
|
809
|
+
ctx.effect(() => ctx.locale.register(namespace, {
|
|
810
|
+
zh,
|
|
811
|
+
en
|
|
812
|
+
}), "ui-sidebar-browser.copy");
|
|
813
|
+
ctx.effect(() => ctx.sidebarRightTabs.register(browserDefinition(t)), "ui-sidebar-browser.type");
|
|
814
|
+
ctx.effect(() => ctx.slots.inject("sidebar.right.pane.tab", () => ctx.slots.register({
|
|
815
|
+
name: "sidebar.right.pane.tab",
|
|
816
|
+
key: BROWSER_ID,
|
|
817
|
+
locale: namespace,
|
|
818
|
+
store,
|
|
819
|
+
inject: (_sessionId, actions) => createBrowserControllers(actions)
|
|
820
|
+
}, BrowserBody)), "ui-sidebar-browser.body");
|
|
821
|
+
ctx.effect(() => ctx.slots.inject("sidebar.right.pane.tab.title", () => ctx.slots.register({
|
|
822
|
+
name: "sidebar.right.pane.tab.title",
|
|
823
|
+
key: BROWSER_ID,
|
|
824
|
+
store
|
|
825
|
+
}, BrowserTitle)), "ui-sidebar-browser.title");
|
|
826
|
+
}
|
|
827
|
+
//#endregion
|
|
828
|
+
exports.apply = apply;
|
|
829
|
+
exports.inject = inject;
|
|
830
|
+
return module.exports;
|
|
831
|
+
}
|
|
832
|
+
});
|
|
833
|
+
|
|
834
|
+
//# sourceMappingURL=client.js.map
|