dsh-auto-open-web 0.1.2

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/lib/client.js ADDED
@@ -0,0 +1,398 @@
1
+ // dsh-auto-open-web — Client bundle(设置卡片,官方控件 + 官方 webServer 数据通道)
2
+ // 格式:window.__ModuleLoader__.load({ id, factory });factory(require) 的
3
+ // require 解析浏览器端冻结模块表(react、@deepseek-ai/dsh-client-ui-primitives 等)。
4
+ //
5
+ // 数据通道:设置卡片经官方 ctx.webServer JSON API 读写(第三方 settings 命名空间
6
+ // 不在客户端白名单,故走自定义 API,与 proxy-router 卡片同一机制):
7
+ // GET /auto-open-web/config → { ok, appWindow, windowKind,
8
+ // browserPath, exitOnWindowClose, writable }
9
+ // POST /auto-open-web/config → { ok, ... } | { ok:false, error }
10
+ // POST /auto-open-web/pick-browser → 原生"浏览"对话框(宿主子进程 + koffi
11
+ // IFileOpenDialog,与官方工作区选择器同机制;不使用 PowerShell),返回
12
+ // { ok:true, path } | { ok:false, cancelled:true, error }
13
+ // 说明:网页 <input type=file> 拿不到本地完整路径,故浏览走宿主原生对话框。
14
+ //
15
+ // 注意:不做客户端 schema 校验——浏览器内置(frontend dist 冻结)的
16
+ // dsh-client-schema-form 副本对本插件 schema 执行 validateDraft 会抛
17
+ // "n is not a function"(与 npm 安装版行为不一致),导致保存按钮永远置灰;
18
+ // 保存由宿主权威校验(normalizeState + scope.update)并持久化到 settings。
19
+ //
20
+ // 官方 API 使用:
21
+ // - 控件:Input / Button(官方 primitives,与全站设计系统一致)
22
+ // - 草稿模型:draft 普通对象,setPath 不可变编辑
23
+ // 卡片外壳保持内置 PluginCard 同款外观(与 proxy-router 卡片同一份 CSS)。
24
+ window.__ModuleLoader__.load({
25
+ id: 'dsh-auto-open-web',
26
+ factory: (require) => {
27
+ var module = { exports: {} }
28
+ var exports = module.exports
29
+ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' })
30
+ let React = require('react')
31
+
32
+ // ---- 内置 PluginCard.module.css(默认卡片外观,原样注入) ----
33
+ var CARD_CSS = '.YyYd_a_card{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:12px;list-style:none;transition:border-color .16s,background .16s}.YyYd_a_card:hover{border-color:var(--dsw-alias-label-dimmed,var(--dsw-alias-border-l2))}.YyYd_a_cardOpen{background:var(--dsw-alias-bg-layer-2);border-color:var(--dsw-alias-label-dimmed,var(--dsw-alias-border-l2))}.YyYd_a_header{appearance:none;width:100%;font:inherit;color:inherit;text-align:left;cursor:pointer;background:0 0;border:0;border-radius:12px;align-items:center;gap:12px;padding:14px 16px;display:flex}.YyYd_a_header:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:-2px}.YyYd_a_headText{flex-direction:column;flex:1;gap:4px;min-width:0;display:flex}.YyYd_a_name{color:var(--dsw-alias-label-primary);font-size:15px;font-weight:600;line-height:1.4}.YyYd_a_description{color:var(--dsw-alias-label-tertiary,var(--dsw-alias-label-secondary));font-size:13px;line-height:1.5}.YyYd_a_chevron{color:var(--dsw-alias-label-tertiary,var(--dsw-alias-label-secondary));flex:none;transition:transform .16s}.YyYd_a_chevronOpen{transform:rotate(180deg)}.YyYd_a_body{border-top:1px solid var(--dsw-alias-border-l2);margin:0 16px;padding-bottom:8px}.YyYd_a_readOnly{color:var(--dsw-alias-label-tertiary,var(--dsw-alias-label-secondary));margin:12px 0 0;font-size:12px;line-height:1.5}.YyYd_a_pending{white-space:nowrap;background:var(--dsw-alias-bg-module-platform,var(--dsw-alias-bg-layer-2));color:var(--dsw-alias-label-secondary);border-radius:999px;flex:none;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}.YyYd_a_footer{border-top:1px solid var(--dsw-alias-border-l2);justify-content:flex-end;align-items:center;gap:8px;padding:12px 0 4px;display:flex}.YyYd_a_failed{min-width:0;color:var(--dsw-alias-label-error,var(--dsw-alias-state-error-primary));flex:1;margin:0;font-size:12px;line-height:1.5}.YyYd_a_discard,.YyYd_a_save{appearance:none;font:inherit;cursor:pointer;border:1px solid #0000;border-radius:8px;padding:5px 14px;font-size:13px;line-height:1.5}.YyYd_a_discard{border-color:var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);background:0 0}.YyYd_a_save{background:var(--dsw-alias-brand-primary);color:var(--dsw-alias-label-inverse,#fff);border-color:var(--dsw-alias-brand-primary)}.YyYd_a_save:hover:not(:disabled){filter:brightness(.95)}.YyYd_a_discard:disabled,.YyYd_a_save:disabled{opacity:.4;cursor:default}'
34
+ var CARD_TAG = 'dsh-auto-open-web/PluginCard.module.css'
35
+ // ---- 内置 ValueField 字段布局样式(label/hint 排版,原样注入) ----
36
+ var FIELDS_CSS = '.At1oFq_field{flex-direction:column;gap:6px;padding:12px 0;display:flex}.At1oFq_field+.At1oFq_field{border-top:1px solid var(--dsw-alias-border-l2)}.At1oFq_head{align-items:center;gap:8px;display:flex}.At1oFq_label{min-width:0;color:var(--dsw-alias-label-primary);flex:1;font-size:13px;font-weight:500;line-height:1.5}.At1oFq_badges{align-items:center;gap:8px;display:inline-flex}.At1oFq_badge{white-space:nowrap;background:var(--dsw-alias-bg-module-platform,var(--dsw-alias-bg-layer-2));color:var(--dsw-alias-label-secondary);border-radius:999px;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}.At1oFq_badgeMuted{white-space:nowrap;color:var(--dsw-alias-label-tertiary,var(--dsw-alias-label-secondary));border-radius:999px;padding:1px 8px;font-size:11px;line-height:17px}.At1oFq_reset{font:inherit;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;padding:0;font-size:12px;line-height:1.5}.At1oFq_reset:hover:not(:disabled){color:var(--dsw-alias-label-primary)}.At1oFq_reset:disabled{cursor:default}.At1oFq_input{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);height:34px;font:inherit;color:var(--dsw-alias-label-primary);border-radius:8px;padding:0 12px;font-size:13px;line-height:1.5}.At1oFq_input:focus-visible{border-color:var(--dsw-alias-brand-primary);outline:none}.At1oFq_input:disabled{color:var(--dsw-alias-label-tertiary,var(--dsw-alias-label-secondary));cursor:default}.At1oFq_inputInvalid{border-color:var(--dsw-alias-label-error,var(--dsw-alias-state-error-primary));}.At1oFq_invalid{color:var(--dsw-alias-label-error,var(--dsw-alias-state-error-primary));margin:0;font-size:12px;line-height:1.5}.At1oFq_hint{color:var(--dsw-alias-label-tertiary,var(--dsw-alias-label-secondary));margin:0;font-size:12px;line-height:1.5}'
37
+ var FIELDS_TAG = 'dsh-auto-open-web/Fields.module.css'
38
+ // ---- 极少量自有布局 CSS(仅行排列/复选行,不改变观感) ----
39
+ var EXTRA_CSS =
40
+ '.aow-row{display:flex;gap:8px;align-items:center}' +
41
+ '.aow-grow{flex:1;min-width:0}' +
42
+ '.aow-check{display:flex;align-items:center;gap:8px;cursor:pointer}' +
43
+ '.aow-check input{accent-color:var(--dsw-alias-brand-primary);width:15px;height:15px;margin:0;flex:none}' +
44
+ '.aow-check input:disabled{cursor:default}' +
45
+ '.aow-test-ok{color:var(--dsw-alias-state-success-primary,#3a8a4a);margin:4px 0 0;font-size:12px;line-height:1.5}' +
46
+ '.aow-test-fail{color:var(--dsw-alias-label-error,var(--dsw-alias-state-error-primary));margin:4px 0 0;font-size:12px;line-height:1.5}'
47
+ var EXTRA_TAG = 'dsh-auto-open-web/Extra.module.css'
48
+ function injectCss(tagId, css) {
49
+ if (typeof document === 'undefined') return
50
+ if (document.querySelector('style[data-plugin-css=' + JSON.stringify(tagId) + ']') !== null) return
51
+ const tag = document.createElement('style')
52
+ tag.dataset.plugin = 'dsh-auto-open-web'
53
+ tag.dataset.pluginCss = tagId
54
+ tag.textContent = css
55
+ document.head.appendChild(tag)
56
+ }
57
+ injectCss(CARD_TAG, CARD_CSS)
58
+ injectCss(FIELDS_TAG, FIELDS_CSS)
59
+ injectCss(EXTRA_TAG, EXTRA_CSS)
60
+
61
+ // ---- 官方共享模块(冻结表):控件 + 草稿/校验模型 ----
62
+ // require 失败时降级而非崩溃(卡片仍然渲染,仅退回基础能力)
63
+ var prim = null
64
+ var form = null
65
+ try {
66
+ prim = require('@deepseek-ai/dsh-client-ui-primitives')
67
+ form = require('@deepseek-ai/dsh-client-schema-form')
68
+ } catch (e) {
69
+ console.error('[auto-open-web] shared modules require failed:', e !== null && e !== undefined && e.message !== undefined ? e.message : String(e))
70
+ }
71
+ var Button = prim !== null && prim.Button !== undefined ? prim.Button : null
72
+ var Input = prim !== null && prim.Input !== undefined ? prim.Input : null
73
+ var ChevronIcon = prim !== null && prim.IconChevronDownOutline14 !== undefined ? prim.IconChevronDownOutline14 : null
74
+ // 注意:不在此处使用官方 rehydrateSchema/validateDraft——浏览器内置(frontend
75
+ // dist 冻结)副本对本插件 schema 校验会抛 "n is not a function" 异常(兼容性
76
+ // 差异),导致 invalid 恒真、保存按钮置灰;宿主侧 normalizeState + scope.update
77
+ // 才是权威校验,本卡片字段也无格式约束,故客户端只做草稿编辑(setPath)。
78
+ var setPath = form !== null ? form.setPath : null
79
+
80
+ // ---- 草稿编辑:优先官方 setPath,不可用时降级为本地实现 ----
81
+ function applySetPath(draftObj, path, value) {
82
+ if (setPath !== null) return setPath(draftObj, path, value)
83
+ const next = Object.assign({}, draftObj)
84
+ next[path[0]] = value
85
+ return next
86
+ }
87
+ // 控件:优先官方 Input/Button,不可用时退回原生元素(用内置字段样式)
88
+ function ProxyInput(props) {
89
+ if (Input !== null) return React.createElement(Input, props)
90
+ const rest = Object.assign({}, props)
91
+ delete rest.className
92
+ return React.createElement('input', Object.assign({ className: 'At1oFq_input' + (props.className !== undefined ? ' ' + props.className : '') }, rest))
93
+ }
94
+ function ProxyButton(props) {
95
+ if (Button !== null) return React.createElement(Button, props)
96
+ const rest = Object.assign({}, props)
97
+ delete rest.className
98
+ return React.createElement('button', Object.assign({ type: 'button', className: 'YyYd_a_discard' + (props.className !== undefined ? ' ' + props.className : '') }, rest))
99
+ }
100
+ function Checkbox(props) {
101
+ return React.createElement('label', { className: 'aow-check' },
102
+ React.createElement('input', {
103
+ type: 'checkbox',
104
+ checked: props.checked === true,
105
+ disabled: props.disabled === true,
106
+ onChange: (event) => props.onChange(event.target.checked)
107
+ }),
108
+ React.createElement('span', { className: 'At1oFq_hint' }, props.children)
109
+ )
110
+ }
111
+ function Radio(props) {
112
+ return React.createElement('label', { className: 'aow-check' },
113
+ React.createElement('input', {
114
+ type: 'radio',
115
+ name: 'auto-open-web-window-kind',
116
+ checked: props.checked === true,
117
+ disabled: props.disabled === true,
118
+ onChange: () => props.onChange(props.value)
119
+ }),
120
+ React.createElement('span', { className: 'At1oFq_hint' }, props.children)
121
+ )
122
+ }
123
+
124
+ function AutoOpenSettingsPage() {
125
+ const [open, setOpen] = React.useState(false)
126
+ const [config, setConfig] = React.useState(null) // { appWindow, windowKind, browserPath, exitOnWindowClose, writable } | null
127
+ const [loadError, setLoadError] = React.useState('')
128
+ const [reloadTick, setReloadTick] = React.useState(0)
129
+ const [draft, setDraft] = React.useState({ appWindow: true, windowKind: 'webview2', browserPath: '', exitOnWindowClose: false })
130
+ const [busy, setBusy] = React.useState(false)
131
+ const [picking, setPicking] = React.useState(false)
132
+ const [testing, setTesting] = React.useState(false)
133
+ const [testResult, setTestResult] = React.useState(null) // { ok, text } | null
134
+ const [failedText, setFailedText] = React.useState('')
135
+
136
+ // 配置加载:官方 webServer API(GET /auto-open-web/config)
137
+ React.useEffect(() => {
138
+ let cancelled = false
139
+ fetch('/auto-open-web/config')
140
+ .then((response) => response.json())
141
+ .then((result) => {
142
+ if (cancelled) return
143
+ if (result === null || result === undefined || result.ok !== true) {
144
+ setLoadError(result !== null && result !== undefined && typeof result.error === 'string' ? result.error : '配置加载失败')
145
+ return
146
+ }
147
+ setConfig({
148
+ appWindow: result.appWindow !== false,
149
+ windowKind: result.windowKind === 'browser' ? 'browser' : 'webview2',
150
+ browserPath: typeof result.browserPath === 'string' ? result.browserPath : '',
151
+ exitOnWindowClose: result.exitOnWindowClose === true,
152
+ writable: result.writable !== false,
153
+ })
154
+ })
155
+ .catch((error) => {
156
+ if (!cancelled) setLoadError('配置加载失败: ' + String(error !== null && error !== undefined && error.message !== undefined ? error.message : error))
157
+ })
158
+ return () => { cancelled = true }
159
+ }, [reloadTick])
160
+
161
+ const saved = config !== null
162
+ ? { appWindow: config.appWindow, windowKind: config.windowKind, browserPath: config.browserPath, exitOnWindowClose: config.exitOnWindowClose }
163
+ : null
164
+
165
+ // 快照值 → 草稿(外部变化/保存后同步)
166
+ React.useEffect(() => {
167
+ if (saved !== null) setDraft({
168
+ appWindow: saved.appWindow,
169
+ windowKind: saved.windowKind,
170
+ browserPath: saved.browserPath,
171
+ exitOnWindowClose: saved.exitOnWindowClose,
172
+ })
173
+ }, [saved !== null ? JSON.stringify(saved) : 'none'])
174
+
175
+ const loading = config === null && loadError === ''
176
+ const writable = config !== null ? config.writable !== false : true
177
+ // 客户端不做 schema 校验(浏览器内置 schema-form 兼容性问题,见上方注释);
178
+ // 保存由宿主权威校验(normalizeState + scope.update)。
179
+ const invalid = false
180
+ const dirty = saved !== null && (
181
+ draft.appWindow !== saved.appWindow ||
182
+ draft.windowKind !== saved.windowKind ||
183
+ draft.browserPath !== saved.browserPath ||
184
+ draft.exitOnWindowClose !== saved.exitOnWindowClose
185
+ )
186
+
187
+ const blocked = saved === null || !dirty || invalid || busy
188
+
189
+ const save = () => {
190
+ if (blocked) return
191
+ setBusy(true)
192
+ setFailedText('')
193
+ fetch('/auto-open-web/config', {
194
+ method: 'POST',
195
+ headers: { 'content-type': 'application/json' },
196
+ body: JSON.stringify({
197
+ appWindow: draft.appWindow,
198
+ windowKind: draft.windowKind,
199
+ browserPath: draft.browserPath,
200
+ exitOnWindowClose: draft.exitOnWindowClose,
201
+ }),
202
+ })
203
+ .then((response) => response.json())
204
+ .then((result) => {
205
+ if (result !== null && result !== undefined && result.ok === true) {
206
+ setConfig((prev) => prev !== null ? {
207
+ appWindow: result.appWindow !== false,
208
+ windowKind: result.windowKind === 'browser' ? 'browser' : 'webview2',
209
+ browserPath: typeof result.browserPath === 'string' ? result.browserPath : '',
210
+ exitOnWindowClose: result.exitOnWindowClose === true,
211
+ writable: prev.writable,
212
+ } : prev)
213
+ } else {
214
+ setFailedText('保存失败: ' + (result !== null && result !== undefined && typeof result.error === 'string' ? result.error : '未知错误'))
215
+ }
216
+ })
217
+ .catch((error) => { setFailedText('保存失败: ' + String(error !== null && error !== undefined && error.message !== undefined ? error.message : error)) })
218
+ .then(() => { setBusy(false) })
219
+ }
220
+
221
+ const discard = () => {
222
+ if (saved !== null) setDraft({
223
+ appWindow: saved.appWindow,
224
+ windowKind: saved.windowKind,
225
+ browserPath: saved.browserPath,
226
+ exitOnWindowClose: saved.exitOnWindowClose,
227
+ })
228
+ setFailedText('')
229
+ }
230
+
231
+ const retryLoad = () => {
232
+ setLoadError('')
233
+ setReloadTick((tick) => tick + 1)
234
+ }
235
+
236
+ // 原生"浏览"对话框:宿主弹出文件选择器,选中后回填路径
237
+ const browse = () => {
238
+ if (!writable || picking) return
239
+ setPicking(true)
240
+ setFailedText('')
241
+ fetch('/auto-open-web/pick-browser', { method: 'POST' })
242
+ .then((response) => response.json())
243
+ .then((result) => {
244
+ if (result !== null && result !== undefined && result.ok === true && typeof result.path === 'string') {
245
+ setDraft(applySetPath(draft, ['browserPath'], result.path))
246
+ } else if (result === null || result === undefined || result.cancelled !== true) {
247
+ setFailedText('选择失败: ' + (result !== null && result !== undefined && typeof result.error === 'string' ? result.error : '未知错误'))
248
+ }
249
+ })
250
+ .catch((error) => { setFailedText('选择失败: ' + String(error !== null && error !== undefined && error.message !== undefined ? error.message : error)) })
251
+ .then(() => { setPicking(false) })
252
+ }
253
+
254
+ // 测试:真正拉起 --app 专用测试实例,确认浏览器可被成功启动
255
+ // (使用当前草稿路径,未保存也能测试;结果展示在字段下方)
256
+ const testBrowser = () => {
257
+ if (testing) return
258
+ setTesting(true)
259
+ setTestResult(null)
260
+ setFailedText('')
261
+ fetch('/auto-open-web/test-browser', {
262
+ method: 'POST',
263
+ headers: { 'content-type': 'application/json' },
264
+ body: JSON.stringify({ browserPath: draft.browserPath }),
265
+ })
266
+ .then((response) => response.json())
267
+ .then((result) => {
268
+ if (result !== null && result !== undefined && result.ok === true) {
269
+ setTestResult({ ok: true, text: typeof result.message === 'string' ? result.message : '浏览器拉起成功' })
270
+ } else {
271
+ setTestResult({ ok: false, text: '测试失败: ' + (result !== null && result !== undefined && typeof result.error === 'string' ? result.error : '未知错误') })
272
+ }
273
+ })
274
+ .catch((error) => { setTestResult({ ok: false, text: '测试失败: ' + String(error !== null && error !== undefined && error.message !== undefined ? error.message : error) }) })
275
+ .then(() => { setTesting(false) })
276
+ }
277
+
278
+ const chevronCls = 'YyYd_a_chevron' + (open ? ' YyYd_a_chevronOpen' : '')
279
+
280
+ return React.createElement('li', { className: 'YyYd_a_card' + (open ? ' YyYd_a_cardOpen' : '') },
281
+ React.createElement('button', {
282
+ type: 'button',
283
+ className: 'YyYd_a_header',
284
+ 'aria-expanded': open,
285
+ 'aria-label': (open ? '折叠' : '展开') + ': 自动打开网页',
286
+ onClick: () => setOpen(!open)
287
+ },
288
+ React.createElement('span', { className: 'YyYd_a_headText' },
289
+ React.createElement('span', { className: 'YyYd_a_name' }, '自动打开网页'),
290
+ React.createElement('span', { className: 'YyYd_a_description' }, 'DSH 启动后自动打开独立应用窗口或网页;可手动指定浏览器位置')
291
+ ),
292
+ dirty ? React.createElement('span', { className: 'YyYd_a_pending' }, '未保存') : null,
293
+ ChevronIcon !== null
294
+ ? React.createElement(ChevronIcon, { className: chevronCls })
295
+ : React.createElement('span', { className: chevronCls }, '▾')
296
+ ),
297
+ open ? React.createElement('div', { className: 'YyYd_a_body' },
298
+ !writable ? React.createElement('p', { className: 'YyYd_a_readOnly', role: 'status' }, '当前配置只读(settings 不可写)。') : null,
299
+ loading
300
+ ? React.createElement('p', { className: 'At1oFq_hint' }, '配置加载中…')
301
+ : (loadError !== ''
302
+ ? React.createElement('div', { className: 'At1oFq_field' },
303
+ React.createElement('p', { className: 'At1oFq_invalid' }, loadError),
304
+ React.createElement('div', { className: 'aow-row' },
305
+ React.createElement(ProxyButton, { variant: 'outline', size: 'sm', onClick: retryLoad }, '重试')
306
+ )
307
+ )
308
+ : React.createElement(React.Fragment, null,
309
+ // ---- 独立应用窗口 ----
310
+ React.createElement('div', { className: 'At1oFq_field' },
311
+ React.createElement('div', { className: 'At1oFq_head' },
312
+ React.createElement('span', { className: 'At1oFq_label' }, '独立应用窗口')
313
+ ),
314
+ React.createElement(Checkbox, { checked: draft.appWindow, disabled: !writable, onChange: (v) => setDraft(applySetPath(draft, ['appWindow'], v)) },
315
+ '启动时自动打开独立应用窗口;关闭后不自动打开任何窗口'
316
+ )
317
+ ),
318
+ // ---- 窗口类型 ----
319
+ React.createElement('div', { className: 'At1oFq_field' },
320
+ React.createElement('div', { className: 'At1oFq_head' },
321
+ React.createElement('span', { className: 'At1oFq_label' }, '窗口类型')
322
+ ),
323
+ React.createElement(Radio, { checked: draft.windowKind === 'webview2', disabled: !writable, value: 'webview2', onChange: (v) => setDraft(applySetPath(draft, ['windowKind'], v)) },
324
+ 'WebView2 宿主(独立进程,任务栏 DSH 图标,随 DSH 退出;需 Windows + WebView2 运行时)'
325
+ ),
326
+ React.createElement(Radio, { checked: draft.windowKind === 'browser', disabled: !writable, value: 'browser', onChange: (v) => setDraft(applySetPath(draft, ['windowKind'], v)) },
327
+ '浏览器应用窗口(--app 专用实例,不与其他浏览器页面共用进程)'
328
+ )
329
+ ),
330
+ // ---- 浏览器可执行文件(仅"浏览器应用窗口"模式使能) ----
331
+ React.createElement('div', { className: 'At1oFq_field' },
332
+ React.createElement('div', { className: 'At1oFq_head' },
333
+ React.createElement('label', { className: 'At1oFq_label', htmlFor: 'auto-open-web-browser-path' }, '浏览器可执行文件')
334
+ ),
335
+ React.createElement('div', { className: 'aow-row' },
336
+ React.createElement(ProxyInput, {
337
+ id: 'auto-open-web-browser-path',
338
+ className: 'aow-grow',
339
+ value: draft.browserPath,
340
+ placeholder: 'C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe',
341
+ disabled: !writable || draft.windowKind !== 'browser',
342
+ onChange: (event) => {
343
+ const next = applySetPath(draft, ['browserPath'], event.target.value)
344
+ setDraft(next)
345
+ }
346
+ }),
347
+ React.createElement(ProxyButton, { variant: 'outline', size: 'sm', disabled: !writable || picking || draft.windowKind !== 'browser', onClick: browse }, picking ? '选择中…' : '浏览'),
348
+ React.createElement(ProxyButton, { variant: 'outline', size: 'sm', disabled: testing || draft.windowKind !== 'browser', onClick: testBrowser }, testing ? '测试中…' : '测试')
349
+ ),
350
+ testResult !== null
351
+ ? React.createElement('p', { className: testResult.ok ? 'aow-test-ok' : 'aow-test-fail', role: 'status' },
352
+ (testResult.ok ? '✓ ' : '✗ ') + testResult.text
353
+ )
354
+ : null,
355
+ React.createElement('p', { className: 'At1oFq_hint' }, '仅"浏览器应用窗口"模式可编辑;留空使用内置候选(Edge → Chrome);路径不存在会被跳过并告警。「测试」会真实拉起一个专用测试实例验证可用性(数秒后自动关闭)')
356
+ ),
357
+ // ---- 窗口关闭时退出 DSH(实验性,默认关闭) ----
358
+ React.createElement('div', { className: 'At1oFq_field' },
359
+ React.createElement('div', { className: 'At1oFq_head' },
360
+ React.createElement('span', { className: 'At1oFq_label' }, '窗口关闭时退出 DSH'),
361
+ React.createElement('span', { className: 'At1oFq_badges' },
362
+ React.createElement('span', { className: 'At1oFq_badge' }, '实验性')
363
+ )
364
+ ),
365
+ React.createElement(Checkbox, { checked: draft.exitOnWindowClose === true, disabled: !writable, onChange: (v) => setDraft(applySetPath(draft, ['exitOnWindowClose'], v)) },
366
+ '关闭自动打开的窗口时,DSH 随之退出(默认关闭)'
367
+ )
368
+ ),
369
+ // ---- 卡片底部(与内置 PluginCard 同款按钮) ----
370
+ React.createElement('div', { className: 'YyYd_a_footer' },
371
+ failedText !== '' ? React.createElement('p', { className: 'YyYd_a_failed', role: 'status' }, failedText) : null,
372
+ React.createElement('button', { type: 'button', className: 'YyYd_a_discard', disabled: !dirty || busy, onClick: discard }, '放弃修改'),
373
+ React.createElement('button', { type: 'button', className: 'YyYd_a_save', disabled: blocked, onClick: save }, busy ? '保存中…' : '保存')
374
+ )
375
+ ))
376
+ ) : null
377
+ )
378
+ }
379
+
380
+ function apply(ctx) {
381
+ const slots = ctx.get('slots')
382
+ if (slots === undefined) {
383
+ console.error('[auto-open-web] slots service unavailable')
384
+ return
385
+ }
386
+ slots.inject('settings.plugin.item', () =>
387
+ slots.register(
388
+ { name: 'settings.plugin.item', id: 'auto-open-web', order: 40, label: '自动打开网页' },
389
+ () => React.createElement(AutoOpenSettingsPage, null)
390
+ )
391
+ )
392
+ }
393
+
394
+ exports.apply = apply
395
+ exports.name = 'auto-open-web'
396
+ return module.exports
397
+ }
398
+ })