dsh-flyout-sidebar 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/client.js ADDED
@@ -0,0 +1,1852 @@
1
+ (function() {
2
+ //#region src/client/jsx.ts
3
+ /** 当前 React 运行时(ESM live binding,initReact 后对所有模块可见) */
4
+ let React;
5
+ function initReact(runtime) {
6
+ React = runtime;
7
+ }
8
+ /**
9
+ * classic JSX 的 Fragment 哨兵:`<></>` 编译为 h(Fragment, …),h 再转发到
10
+ * React.Fragment。类型标注为 ExoticComponent,让 tsc 接受 <Fragment> 作为
11
+ * JSX 元素类型;运行时值始终是 Symbol 哨兵。
12
+ */
13
+ const Fragment = Symbol("dsh-flyout-sidebar.Fragment");
14
+ /** JSX 工厂:转发 React.createElement,并处理 Fragment 哨兵 */
15
+ function h(type, props, ...children) {
16
+ const resolved = type === Fragment ? React.Fragment : type;
17
+ return React.createElement(resolved, props ?? null, ...children);
18
+ }
19
+ //#endregion
20
+ //#region src/client/runtime.ts
21
+ let ctx;
22
+ function initClient(context) {
23
+ ctx = context;
24
+ }
25
+ async function getJson(url) {
26
+ return (await fetch(url)).json();
27
+ }
28
+ /** 组装查询串:空值参数不下发(与旧版 URL 形态一致) */
29
+ function qs(...pairs) {
30
+ const q = pairs.filter(([, v]) => v !== "").map(([k, v]) => k + "=" + encodeURIComponent(v));
31
+ return q.length ? "?" + q.join("&") : "";
32
+ }
33
+ /** host RPC 桥:与 host 侧 /flyout-sidebar/* 路由一一对应 */
34
+ const host = {
35
+ gitStatus(sessionId) {
36
+ return getJson("/flyout-sidebar/gitstatus" + qs(["sessionId", sessionId]));
37
+ },
38
+ gitDiff(path, sessionId) {
39
+ return getJson("/flyout-sidebar/gitdiff" + qs(["path", path], ["sessionId", sessionId]));
40
+ },
41
+ readArtifact(path) {
42
+ return getJson("/flyout-sidebar/content" + qs(["path", path]));
43
+ },
44
+ listDir(path, sessionId) {
45
+ return getJson("/flyout-sidebar/listdir" + qs(["path", path], ["sessionId", sessionId]));
46
+ }
47
+ };
48
+ //#endregion
49
+ //#region src/client/styles.ts
50
+ /**
51
+ * Client 侧:注入的样式表。
52
+ *
53
+ * 一次性插入 <style id="dsh-flyout-sidebar-styles">;CSS 变量遵循 DSH 的
54
+ * --dsw-alias-* 设计令牌,深浅主题自动跟随。
55
+ */
56
+ const styleCss = `
57
+ html #root {
58
+ margin-right: calc(var(--dsh-sidebar-width, 0px) + var(--dsh-flyout-sidebar-width, 0px));
59
+ transition: margin-right var(--ds-transition-duration-slow, 200ms) var(--ds-ease-in-out, ease);
60
+ }
61
+ body[data-dsh-flyout-dragging] #root {
62
+ transition: none;
63
+ }
64
+ header:has([data-slot="conversation.session.header.utilities"]) {
65
+ padding-right: max(28px, calc(60px - var(--dsh-flyout-sidebar-width, 0px)));
66
+ transition: padding-right var(--ds-transition-duration-slow, 200ms) var(--ds-ease-in-out, ease);
67
+ }
68
+ @media (prefers-reduced-motion: reduce) {
69
+ html #root { transition: none; }
70
+ header:has([data-slot="conversation.session.header.utilities"]) { transition: none; }
71
+ }
72
+ .artifacts-preview-overlay {
73
+ position: fixed; top: 0; bottom: 0; left: 0;
74
+ right: calc(var(--dsh-sidebar-width, 0px) + var(--dsh-flyout-sidebar-width, 0px));
75
+ z-index: 9998;
76
+ display: flex; flex-direction: column; min-width: 0;
77
+ background: var(--dsw-alias-bg-base);
78
+ color: var(--dsw-alias-label-primary);
79
+ border-right: 1px solid var(--dsw-alias-border-l1);
80
+ box-shadow: var(--dsw-shadow-lv2);
81
+ pointer-events: auto;
82
+ font-family: var(--dsw-font-family, -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif);
83
+ font-size: 13px; line-height: 1.5;
84
+ transition: right var(--ds-transition-duration-slow, 200ms) var(--ds-ease-in-out, ease);
85
+ }
86
+ body[data-dsh-flyout-dragging] .artifacts-preview-overlay { transition: none; }
87
+ .artifacts-preview-overlay-tabs {
88
+ flex: none; display: flex; align-items: stretch; height: 28px;
89
+ background: var(--dsw-alias-bg-layer-1);
90
+ border-bottom: 1px solid var(--dsw-alias-border-l2);
91
+ }
92
+ .artifacts-ptabs-scroll {
93
+ flex: 1 1 auto; display: flex; align-items: stretch; min-width: 0;
94
+ overflow-x: auto; overflow-y: hidden; scrollbar-width: thin;
95
+ }
96
+ .artifacts-ptab {
97
+ flex: none; display: flex; align-items: center; gap: 6px;
98
+ max-width: 220px; padding: 0 6px 0 12px; cursor: pointer;
99
+ border-right: 1px solid var(--dsw-alias-border-l1);
100
+ color: var(--dsw-alias-label-secondary); font-size: 12px; line-height: 28px;
101
+ user-select: none;
102
+ }
103
+ .artifacts-ptab:hover { background: var(--dsw-alias-interactive-bg-hover); color: var(--dsw-alias-label-primary); }
104
+ .artifacts-ptab.is-active {
105
+ background: var(--dsw-alias-bg-base); color: var(--dsw-alias-label-primary);
106
+ box-shadow: inset 0 -2px 0 var(--dsw-alias-state-business-primary);
107
+ }
108
+ .artifacts-ptab-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
109
+ .artifacts-ptab-close {
110
+ flex: none; width: 18px; height: 18px; padding: 0; line-height: 1; font-size: 13px;
111
+ display: inline-flex; align-items: center; justify-content: center;
112
+ border: none; background: transparent; color: inherit; cursor: pointer; border-radius: 4px;
113
+ }
114
+ .artifacts-ptab-close:hover { background: var(--dsw-alias-interactive-bg-hover-accent, rgba(0, 0, 0, 0.08)); }
115
+ .artifacts-preview-hide {
116
+ flex: none; width: 32px; display: inline-flex; align-items: center; justify-content: center;
117
+ border: none; border-left: 1px solid var(--dsw-alias-border-l1); background: transparent;
118
+ color: var(--dsw-alias-label-secondary); cursor: pointer; padding: 0;
119
+ }
120
+ .artifacts-preview-hide:hover { background: var(--dsw-alias-interactive-bg-hover); color: var(--dsw-alias-label-primary); }
121
+ .artifacts-preview-overlay .artifacts-preview-body { flex: 1; min-height: 0; }
122
+ .artifacts-preview-overlay .artifacts-img { max-height: none; }
123
+ .artifacts-panel {
124
+ position: fixed; top: 0; right: var(--dsh-sidebar-width, 0px); bottom: 0; width: 30vw; max-width: calc(100vw - 24px); min-width: 0;
125
+ display: flex; flex-direction: column;
126
+ background: var(--dsw-alias-bg-base);
127
+ color: var(--dsw-alias-label-primary);
128
+ border-left: 1px solid var(--dsw-alias-border-l1);
129
+ box-shadow: var(--dsw-shadow-lv2);
130
+ pointer-events: auto; z-index: 9999;
131
+ font-family: var(--dsw-font-family, -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif);
132
+ font-size: 13px; line-height: 1.5;
133
+ --dsh-scrollbar-thumb: var(--dsw-alias-scrollbar-bg-l2);
134
+ --dsh-scrollbar-thumb-hover: var(--dsw-alias-scrollbar-hover-l2);
135
+ transition: right var(--ds-transition-duration-slow, 200ms) var(--ds-ease-in-out, ease);
136
+ }
137
+ .artifacts-panel.artifacts-resizing { transition: none; user-select: none; }
138
+ .artifacts-head {
139
+ position: relative; display: flex; align-items: center; gap: 4px; padding: 0 6px; flex: none; height: 28px;
140
+ border-bottom: 1px solid var(--dsw-alias-border-l2);
141
+ background: var(--dsw-alias-bg-layer-1);
142
+ }
143
+ .artifacts-head-left { display: flex; align-items: center; gap: 4px; flex: none; }
144
+ .artifacts-spacer { flex: 1; }
145
+ .artifacts-toggle {
146
+ display: inline-flex; align-items: center; justify-content: center; padding: 4px; line-height: 0;
147
+ border: none; background: transparent; border-radius: 6px;
148
+ color: var(--dsw-alias-label-secondary); cursor: pointer;
149
+ }
150
+ .artifacts-toggle:hover { color: var(--dsw-alias-label-primary); }
151
+ .artifacts-link { color: var(--dsw-alias-state-business-primary); text-decoration: none; padding: 4px; border-radius: 6px; display: inline-flex; align-items: center; }
152
+ .artifacts-link:hover { background: var(--dsw-alias-interactive-bg-hover); }
153
+ .artifacts-iconbtn {
154
+ background: transparent; border: 1px solid var(--dsw-alias-border-l2);
155
+ color: var(--dsw-alias-label-secondary); border-radius: 6px; padding: 2px 8px;
156
+ cursor: pointer; font-size: 12px;
157
+ }
158
+ .artifacts-iconbtn:hover { background: var(--dsw-alias-interactive-bg-hover); }
159
+ .artifacts-main { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; }
160
+ .artifacts-body { flex: 0 0 auto; min-height: 0; overflow-y: auto; }
161
+ .artifacts-empty { padding: 28px 16px; color: var(--dsw-alias-label-tertiary); text-align: center; }
162
+ .artifacts-item {
163
+ display: flex; align-items: stretch; width: 100%; padding: 0;
164
+ border-bottom: 1px solid var(--dsw-alias-border-l2);
165
+ background: transparent; color: inherit; cursor: default;
166
+ }
167
+ .artifacts-item:hover { background: var(--dsw-alias-interactive-bg-hover); }
168
+ .artifacts-item.is-active { background: var(--dsw-alias-interactive-bg-hover); box-shadow: inset 3px 0 0 var(--dsw-alias-state-business-primary); }
169
+ .artifacts-item-row { display: flex; align-items: center; gap: 8px; }
170
+ .artifacts-item-main { flex: 1; min-width: 0; text-align: left; padding: 9px 12px; border: none; background: transparent; color: inherit; cursor: pointer; font: inherit; }
171
+ .artifacts-item-actions { display: flex; align-items: center; gap: 2px; padding-right: 6px; opacity: 0; }
172
+ .artifacts-item:hover .artifacts-item-actions { opacity: 1; }
173
+ .artifacts-minibtn { border: none; background: transparent; color: var(--dsw-alias-label-tertiary); cursor: pointer; font-size: 12px; padding: 2px 6px; border-radius: 4px; }
174
+ .artifacts-minibtn:hover { background: var(--dsw-alias-interactive-bg-hover); color: var(--dsw-alias-label-primary); }
175
+ .artifacts-notice { color: var(--dsw-alias-state-business-primary); font-size: 12px; }
176
+ .artifacts-hint { padding: 24px 16px; color: var(--dsw-alias-label-tertiary); text-align: center; }
177
+ .artifacts-error { padding: 16px; color: var(--dsw-alias-state-error-primary); font-family: var(--dsh-font-mono, monospace); word-break: break-all; }
178
+ .artifacts-corner-btn {
179
+ position: fixed; top: 10px; right: calc(var(--dsh-sidebar-width, 0px) + var(--dsh-flyout-sidebar-width, 0px) + 12px);
180
+ z-index: 10000; width: 36px; height: 36px; padding: 0;
181
+ border: none; background: transparent; color: var(--dsw-alias-label-secondary);
182
+ cursor: pointer; align-items: center; justify-content: center; display: inline-flex;
183
+ transition: right var(--ds-transition-duration-slow, 200ms) var(--ds-ease-in-out, ease), color .15s;
184
+ }
185
+ .artifacts-corner-btn:hover { color: var(--dsw-alias-label-primary); }
186
+ .artifacts-preview-body { flex: 1; min-height: 0; overflow-y: auto; position: relative; }
187
+ .artifacts-img { display: block; max-width: 100%; max-height: 70vh; object-fit: contain; margin: 12px; }
188
+ .artifacts-iframe { width: 100%; height: 100%; min-height: 360px; border: 0; background: #fff; }
189
+ .artifacts-pdf { width: 100%; height: 100%; min-height: 360px; border: 0; background: #fff; display: block; }
190
+ .artifacts-pdfview { position: absolute; top: 0; right: 0; bottom: 0; left: 0; display: flex; flex-direction: column; background: #525659; }
191
+ .artifacts-pdfview-bar { flex: none; display: flex; align-items: center; gap: 6px; height: 34px; padding: 0 8px; background: var(--dsw-alias-bg-layer-1); border-bottom: 1px solid var(--dsw-alias-border-l2); }
192
+ .artifacts-pdfview-btn { min-width: 24px; height: 22px; border: 1px solid var(--dsw-alias-border-l2); background: transparent; color: var(--dsw-alias-label-secondary); border-radius: 5px; cursor: pointer; font: inherit; font-size: 13px; line-height: 1; padding: 0 6px; }
193
+ .artifacts-pdfview-btn:hover:not(:disabled) { background: var(--dsw-alias-interactive-bg-hover); color: var(--dsw-alias-label-primary); }
194
+ .artifacts-pdfview-btn:disabled { opacity: .4; cursor: default; }
195
+ .artifacts-pdfview-zoom { font-size: 12px; color: var(--dsw-alias-label-secondary); min-width: 40px; text-align: center; }
196
+ .artifacts-pdfview-page { font-size: 12px; color: var(--dsw-alias-label-secondary); min-width: 44px; text-align: center; }
197
+ .artifacts-pdfview-spacer { flex: 1; }
198
+ .artifacts-pdfview-scroll { flex: 1; min-height: 0; overflow: auto; padding: 12px; }
199
+ .artifacts-pdfview-canvas { display: block; margin: 0 auto; background: #fff; box-shadow: 0 2px 10px rgba(0,0,0,.35); }
200
+ .artifacts-markdown { padding: 12px 14px; line-height: 1.6; word-wrap: break-word; font-size: 13px; }
201
+ .artifacts-markdown h1, .artifacts-markdown h2, .artifacts-markdown h3, .artifacts-markdown h4, .artifacts-markdown h5, .artifacts-markdown h6 { margin: 14px 0 8px; line-height: 1.3; }
202
+ .artifacts-markdown h1 { font-size: 1.45em; border-bottom: 1px solid var(--dsw-alias-border-l2); padding-bottom: 6px; }
203
+ .artifacts-markdown h2 { font-size: 1.25em; border-bottom: 1px solid var(--dsw-alias-border-l1); padding-bottom: 4px; }
204
+ .artifacts-markdown code { background: var(--dsw-alias-bg-layer-1); padding: 1px 5px; border-radius: 4px; font-family: var(--dsh-font-mono, ui-monospace, monospace); font-size: 0.9em; }
205
+ .artifacts-markdown pre { background: var(--dsw-alias-bg-layer-1); padding: 10px 12px; border-radius: 6px; overflow: auto; }
206
+ .artifacts-markdown pre code { background: transparent; padding: 0; }
207
+ .artifacts-markdown img { max-width: 100%; }
208
+ .artifacts-markdown blockquote { border-left: 3px solid var(--dsw-alias-border-l2); margin: 8px 0; padding: 2px 12px; color: var(--dsw-alias-label-secondary); }
209
+ .artifacts-markdown ul, .artifacts-markdown ol { padding-left: 24px; }
210
+ .artifacts-markdown a { color: var(--dsw-alias-state-business-primary); }
211
+ .artifacts-diff { border-top: 1px solid var(--dsw-alias-border-l2); }
212
+ .artifacts-diff-block { border-bottom: 1px solid var(--dsw-alias-border-l1); }
213
+ .artifacts-diff-label { font-size: 11px; padding: 4px 12px; font-weight: 600; }
214
+ .artifacts-diff-del .artifacts-diff-label { color: var(--dsw-alias-state-error-primary); background: rgba(236,19,19,0.06); }
215
+ .artifacts-diff-add .artifacts-diff-label { color: var(--dsw-alias-state-success-primary); background: rgba(34,197,94,0.08); }
216
+ .artifacts-diff-pre { margin: 0; padding: 8px 12px; font: 12px/1.5 var(--dsh-font-mono, ui-monospace, monospace); white-space: pre-wrap; word-break: break-word; color: var(--dsw-alias-label-secondary); }
217
+ .artifacts-diff-del .artifacts-diff-pre { background: rgba(236,19,19,0.05); }
218
+ .artifacts-diff-add .artifacts-diff-pre { background: rgba(34,197,94,0.06); }
219
+ .artifacts-resize { position: absolute; left: -4px; top: 0; bottom: 0; width: 8px; cursor: col-resize; z-index: 3; touch-action: none; }
220
+ .artifacts-resize::after { content: ''; position: absolute; left: 3px; top: 0; bottom: 0; width: 2px; background: transparent; transition: background .15s; }
221
+ .artifacts-resize:hover::after, .artifacts-resize:active::after { background: var(--dsw-alias-interactive-bg-hover-accent); }
222
+ .artifacts-viewbtn { display: inline-flex; align-items: center; justify-content: center; height: 26px; width: 26px; padding: 0; border: none; background: transparent; color: var(--dsw-alias-label-secondary); cursor: pointer; border-radius: 6px; }
223
+ .artifacts-viewbtn:hover { background: var(--dsw-alias-interactive-bg-hover); color: var(--dsw-alias-label-primary); }
224
+ .artifacts-viewbtn.is-active { color: var(--dsw-alias-state-business-primary); }
225
+ .artifacts-git-badge { font-size: 10px; font-weight: 700; width: 16px; height: 16px; flex: none; display: inline-flex; align-items: center; justify-content: center; border-radius: 4px; font-family: var(--dsh-font-mono, ui-monospace, monospace); }
226
+ .artifacts-git-badge-M { background: var(--dsw-alias-state-warn-tertiary); color: var(--dsw-alias-state-warn-label); }
227
+ .artifacts-git-badge-A { background: var(--dsw-alias-state-success-tertiary); color: var(--dsw-alias-state-success-primary); }
228
+ .artifacts-git-badge-D { background: rgba(236,19,19,0.1); color: var(--dsw-alias-state-error-primary); }
229
+ .artifacts-git-badge-R { background: var(--dsw-alias-state-business-tertiary, rgba(65,118,230,0.1)); color: var(--dsw-alias-state-business-primary); }
230
+ .artifacts-git-badge-U { background: var(--dsw-alias-interactive-bg-hover); color: var(--dsw-alias-label-tertiary); }
231
+ .artifacts-git-orig { font-size: 11px; color: var(--dsw-alias-label-tertiary); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
232
+ .artifacts-git-error { padding: 14px 12px; word-break: break-all; }
233
+ .artifacts-gitdiff { font-family: var(--dsh-font-mono, ui-monospace, SFMono-Regular, Menlo, monospace); font-size: 12px; line-height: 1.6; }
234
+ .gd-line { white-space: pre-wrap; word-break: break-all; padding: 0 12px; }
235
+ .gd-meta { color: var(--dsw-alias-label-tertiary); background: var(--dsw-alias-bg-layer-1); padding: 2px 12px; }
236
+ .gd-hunk { color: var(--dsw-alias-state-business-primary); background: var(--dsw-alias-state-business-tertiary, rgba(65,118,230,0.08)); padding: 2px 12px; }
237
+ .gd-add { color: #1a7f37; background: rgba(34,197,94,0.08); }
238
+ .gd-del { color: #cf222e; background: rgba(236,19,19,0.07); }
239
+ body[data-ds-dark-theme] .gd-add { color: #69db7c; }
240
+ body[data-ds-dark-theme] .gd-del { color: #faa2c1; }
241
+ .artifacts-tree { display: flex; flex-direction: column; height: 100%; min-height: 0; }
242
+ .artifacts-tree-body { flex: 1; min-height: 0; overflow-y: auto; padding: 2px 6px 8px; }
243
+ .artifacts-tree-row { box-sizing: border-box; width: 100%; height: 34px; font: var(--dsw-font-s-14); color: var(--dsw-alias-label-primary); text-align: left; cursor: pointer; white-space: nowrap; background: transparent; border: none; border-radius: 8px; align-items: center; gap: 6px; padding: 0 8px; display: flex; animation: artifacts-row-in .15s var(--ds-ease-in-out, ease); }
244
+ .artifacts-tree-row:hover { background: var(--dsw-alias-interactive-bg-hover); }
245
+ .artifacts-tree-dir { font: var(--dsw-font-s-strong-14); }
246
+ .artifacts-tree-hidden { opacity: .45; }
247
+ .artifacts-tree-name { flex: 1; min-width: 0; text-overflow: ellipsis; overflow: hidden; }
248
+ .artifacts-tree-row.is-selected { background: var(--dsw-alias-interactive-bg-active); }
249
+ .artifacts-tree-ref { border: 1px solid var(--dsw-alias-border-l1); background: var(--dsw-alias-bg-layer-2); height: 20px; color: var(--dsw-alias-label-tertiary); font: var(--dsw-font-xxxs-strong-11); cursor: pointer; border-radius: 999px; flex: none; align-items: center; padding: 0 8px; display: none; }
250
+ .artifacts-tree-ref:hover { background: var(--dsw-alias-interactive-bg-hover); color: var(--dsw-alias-label-primary); }
251
+ .artifacts-tree-row:hover .artifacts-tree-ref, .artifacts-tree-row:focus-within .artifacts-tree-ref { display: inline-flex; }
252
+ .artifacts-tree-copied { font: var(--dsw-font-xxxs-11); color: var(--dsw-alias-label-tertiary); flex: none; }
253
+ .artifacts-tree-loading { cursor: default; color: var(--dsw-alias-label-tertiary); font-size: 12px; }
254
+ .artifacts-tree-error { cursor: default; color: var(--dsw-alias-state-error-primary); font-size: 12px; }
255
+ @keyframes artifacts-row-in { 0% { opacity: 0 } }
256
+ .artifacts-settings { display: flex; flex-direction: column; gap: 14px; width: 100%; height: 100%; min-height: 0; overflow-y: auto; padding-bottom: 24px; }
257
+ .artifacts-setintro { color: var(--dsw-alias-label-tertiary); margin: 0; padding: 0 2px; font-size: 13px; line-height: 20px; }
258
+ .artifacts-setgroup { border: 1px solid var(--dsw-alias-border-l2); background: var(--dsw-alias-bg-layer-3); border-radius: 16px; padding: 6px 20px; display: flex; flex-direction: column; flex: none; }
259
+ .artifacts-setrow { border-bottom: 1px solid var(--dsw-alias-border-l2); justify-content: space-between; align-items: center; gap: 16px; padding: 12px 2px; display: flex; }
260
+ .artifacts-setrow:last-child { border-bottom: none; }
261
+ .artifacts-settext { flex-direction: column; gap: 4px; min-width: 0; display: flex; }
262
+ .artifacts-settitle { color: var(--dsw-alias-label-primary); font-size: 14px; line-height: 22px; }
263
+ .artifacts-setdesc { color: var(--dsw-alias-label-tertiary); font-size: 12px; line-height: 18px; }
264
+ .artifacts-switch { cursor: pointer; flex: none; display: inline-flex; position: relative; }
265
+ .artifacts-switch input { opacity: 0; width: 1px; height: 1px; margin: 0; position: absolute; }
266
+ .artifacts-switch-track { box-sizing: border-box; border: 1px solid var(--dsw-alias-border-l2); background: var(--dsw-alias-bg-layer-1); border-radius: 10px; align-items: center; width: 36px; height: 20px; padding: 2px; transition: background .15s, border-color .15s; display: inline-flex; }
267
+ .artifacts-switch-thumb { background: var(--dsw-alias-label-secondary); border-radius: 50%; width: 14px; height: 14px; transition: transform .15s, background .15s; display: block; }
268
+ .artifacts-switch:hover .artifacts-switch-track { border-color: var(--dsw-alias-label-dimmed); }
269
+ .artifacts-switch input:checked + .artifacts-switch-track { border-color: var(--dsw-alias-button-primary-fill); background: var(--dsw-alias-button-primary-fill); }
270
+ .artifacts-switch input:checked + .artifacts-switch-track .artifacts-switch-thumb { background: var(--dsw-alias-bg-layer-3); transform: translate(16px); }
271
+ .artifacts-switch input:focus-visible + .artifacts-switch-track { outline: 2px solid var(--dsw-alias-state-business-primary); outline-offset: 2px; }
272
+ .artifacts-setcontrol { flex: none; align-items: center; gap: 6px; display: flex; }
273
+ .artifacts-widthinput { width: 76px; border: 1px solid var(--dsw-alias-border-l2); background: var(--dsw-alias-bg-layer-1); color: var(--dsw-alias-label-primary); font: inherit; border-radius: 6px; padding: 4px 8px; }
274
+ .artifacts-suffix { color: var(--dsw-alias-label-secondary); font-size: 14px; line-height: 22px; }
275
+ .artifacts-code { display: flex; flex-direction: column; height: 100%; min-height: 0; }
276
+ .artifacts-code-scroll { flex: 1; min-height: 0; overflow: auto; display: flex; align-items: flex-start; background: var(--shiki-background, var(--dsw-alias-markdown-code-block, var(--dsw-alias-bg-layer-1))); }
277
+ .artifacts-code-gutter { flex: none; min-width: 2.2em; margin: 0; padding: 12px 6px 12px 8px; text-align: right; color: var(--dsw-alias-label-tertiary); border-right: 1px solid var(--dsw-alias-border-l1); position: sticky; left: 0; user-select: none; background: var(--shiki-background, var(--dsw-alias-markdown-code-block, var(--dsw-alias-bg-layer-1))); font: 12px/1.6 var(--dsh-font-mono, ui-monospace, SFMono-Regular, Menlo, monospace); white-space: pre; }
278
+ .artifacts-code-pre { flex: 1; margin: 0; padding: 12px; background: var(--shiki-background, var(--dsw-alias-markdown-code-block, var(--dsw-alias-bg-layer-1))); color: var(--shiki-foreground, var(--dsw-alias-label-primary)); font: 12px/1.6 var(--dsh-font-mono, ui-monospace, SFMono-Regular, Menlo, monospace); white-space: pre; }
279
+ .artifacts-code-pre code { font: inherit; color: inherit; }
280
+ .artifacts-code-line { display: block; }
281
+ .tok-comment { color: #868e96; }
282
+ .tok-string { color: #2f9e44; }
283
+ .tok-number, .tok-bool, .tok-variable, .tok-hex, .tok-attr { color: #e8590c; }
284
+ .tok-keyword, .tok-important, .tok-atrule { color: #d6336c; }
285
+ .tok-function, .tok-decorator { color: #6741d9; }
286
+ .tok-class, .tok-builtin, .tok-tag, .tok-key { color: #1971c2; }
287
+ .tok-property { color: #495057; }
288
+ body[data-ds-dark-theme] .tok-comment { color: #adb5bd; }
289
+ body[data-ds-dark-theme] .tok-string { color: #69db7c; }
290
+ body[data-ds-dark-theme] .tok-number, body[data-ds-dark-theme] .tok-bool, body[data-ds-dark-theme] .tok-variable, body[data-ds-dark-theme] .tok-hex, body[data-ds-dark-theme] .tok-attr { color: #ffa94d; }
291
+ body[data-ds-dark-theme] .tok-keyword, body[data-ds-dark-theme] .tok-important, body[data-ds-dark-theme] .tok-atrule { color: #faa2c1; }
292
+ body[data-ds-dark-theme] .tok-function, body[data-ds-dark-theme] .tok-decorator { color: #b197fc; }
293
+ body[data-ds-dark-theme] .tok-class, body[data-ds-dark-theme] .tok-builtin, body[data-ds-dark-theme] .tok-tag, body[data-ds-dark-theme] .tok-key { color: #74c0fc; }
294
+ body[data-ds-dark-theme] .tok-property { color: #ced4da; }
295
+ `;
296
+ /** 注入样式(幂等:已存在则跳过) */
297
+ function insertStyles() {
298
+ if (typeof document === "undefined") return;
299
+ const id = "dsh-flyout-sidebar-styles";
300
+ if (document.getElementById(id)) return;
301
+ const el = document.createElement("style");
302
+ el.id = id;
303
+ el.textContent = styleCss;
304
+ document.head.appendChild(el);
305
+ }
306
+ //#endregion
307
+ //#region src/shared/ext.js
308
+ /**
309
+ * 共享:扩展名 → 预览类型 判定。
310
+ *
311
+ * 本目录的三个模块被同时用于三处,因此必须保持「可移植源码」约束:
312
+ * 1. tsdown 打包进 host 侧(Node);
313
+ * 2. tsdown 打包进 client 侧(浏览器 React bundle);
314
+ * 3. tsdown 构建期经 `?raw` 读入原始文本,剥离 import/export 后内联进独立
315
+ * 弹出页 /flyout-sidebar 的经典 <script>(见 src/host/page.ts)。
316
+ *
317
+ * 因此这些文件只能使用 JSDoc 标注类型(不得出现 TS 语法注记),且不得引入
318
+ * 本目录之外的依赖。highlight/markdown 同理。
319
+ */
320
+ /** @type {Record<string, number>} */
321
+ const EXT_IMAGE = {
322
+ png: 1,
323
+ jpg: 1,
324
+ jpeg: 1,
325
+ gif: 1,
326
+ webp: 1,
327
+ svg: 1,
328
+ bmp: 1,
329
+ ico: 1,
330
+ avif: 1
331
+ };
332
+ /** @type {Record<string, number>} */
333
+ const EXT_PDF = { pdf: 1 };
334
+ /** @type {Record<string, number>} */
335
+ const EXT_MARKDOWN = {
336
+ md: 1,
337
+ markdown: 1,
338
+ mdx: 1,
339
+ mdown: 1
340
+ };
341
+ /** @type {Record<string, number>} */
342
+ const EXT_HTML = {
343
+ html: 1,
344
+ htm: 1,
345
+ xhtml: 1
346
+ };
347
+ /**
348
+ * @param {string} path
349
+ * @returns {'image' | 'pdf' | 'markdown' | 'html' | 'text'}
350
+ */
351
+ function extType(path) {
352
+ const ext = fileExt(path);
353
+ if (EXT_IMAGE[ext]) return "image";
354
+ if (EXT_PDF[ext]) return "pdf";
355
+ if (EXT_MARKDOWN[ext]) return "markdown";
356
+ if (EXT_HTML[ext]) return "html";
357
+ return "text";
358
+ }
359
+ /** @param {string} path @returns {string} 小写扩展名(无扩展名时为 '') */
360
+ function fileExt(path) {
361
+ const m = /\.([^.]+)$/.exec(String(path || ""));
362
+ return m ? (m[1] || "").toLowerCase() : "";
363
+ }
364
+ //#endregion
365
+ //#region src/client/store.ts
366
+ const basename = (p) => {
367
+ const parts = String(p).split("/");
368
+ return parts[parts.length - 1] || p;
369
+ };
370
+ /** 当前会话 id(读自客户端会话库;文件树把它传给 host 以定位工作区) */
371
+ function currentSessionId() {
372
+ try {
373
+ const list = ctx.get("sessions")?.list;
374
+ if (list && typeof list.getSnapshot === "function") {
375
+ const snap = list.getSnapshot();
376
+ const id = snap ? snap.current != null ? snap.current : snap.active : void 0;
377
+ return typeof id === "string" ? id : "";
378
+ }
379
+ } catch {}
380
+ return "";
381
+ }
382
+ /**
383
+ * 把 `@path` 写进当前会话输入框草稿。成功返回 true;输入 API 不可用时返回
384
+ * false(调用方回退到剪贴板复制)。
385
+ */
386
+ function quoteToComposer(path) {
387
+ try {
388
+ const sessions = ctx.get("sessions");
389
+ const conversation = ctx.get("conversation");
390
+ if (!sessions || !conversation) return false;
391
+ const list = sessions.list;
392
+ let sessionId;
393
+ if (list && typeof list.getSnapshot === "function") {
394
+ const snap = list.getSnapshot();
395
+ const id = snap ? snap.current != null ? snap.current : snap.active : void 0;
396
+ if (typeof id === "string") sessionId = id;
397
+ }
398
+ if (sessionId == null) return false;
399
+ const actx = typeof sessions.scope === "function" ? sessions.scope(sessionId) : void 0;
400
+ if (!actx) return false;
401
+ const input = conversation.input && typeof conversation.input.for === "function" ? conversation.input.for(actx) : void 0;
402
+ if (!input || typeof input.setDraft !== "function") return false;
403
+ let draft = "";
404
+ try {
405
+ if (input.state && typeof input.state.getSnapshot === "function") draft = input.state.getSnapshot()?.draft || "";
406
+ } catch {}
407
+ const text = "@" + path;
408
+ input.setDraft(draft && draft.trim() !== "" ? draft + " " + text : text);
409
+ return true;
410
+ } catch {
411
+ return false;
412
+ }
413
+ }
414
+ const fallbackCopy = (text) => {
415
+ try {
416
+ const ta = document.createElement("textarea");
417
+ ta.value = text;
418
+ document.body.appendChild(ta);
419
+ ta.select();
420
+ document.execCommand("copy");
421
+ document.body.removeChild(ta);
422
+ } catch {}
423
+ };
424
+ /** 面板开/关共享状态:角落触发按钮与浮动面板之间的单一事实来源 */
425
+ const store = {
426
+ open: false,
427
+ listeners: [],
428
+ setOpen(v) {
429
+ if (this.open === v) return;
430
+ this.open = v;
431
+ for (const fn of this.listeners) try {
432
+ fn(v);
433
+ } catch {}
434
+ },
435
+ toggle() {
436
+ this.setOpen(!this.open);
437
+ },
438
+ subscribe(fn) {
439
+ this.listeners.push(fn);
440
+ return () => {
441
+ this.listeners = this.listeners.filter((f) => f !== fn);
442
+ };
443
+ }
444
+ };
445
+ const useOpen = () => {
446
+ const [open, setOpen] = React.useState(store.open);
447
+ React.useEffect(() => store.subscribe(setOpen), []);
448
+ return open;
449
+ };
450
+ const SETTINGS_KEY = "dsh-flyout-sidebar:settings";
451
+ const DEFAULT_SETTINGS = {
452
+ autoRefresh: true,
453
+ minPanelWidth: 20,
454
+ showFileTree: true,
455
+ defaultOpen: true
456
+ };
457
+ function loadSettings() {
458
+ try {
459
+ const raw = localStorage.getItem(SETTINGS_KEY);
460
+ if (raw) {
461
+ const parsed = JSON.parse(raw);
462
+ if (parsed && typeof parsed === "object") return {
463
+ ...DEFAULT_SETTINGS,
464
+ ...parsed
465
+ };
466
+ }
467
+ } catch {}
468
+ return { ...DEFAULT_SETTINGS };
469
+ }
470
+ const settingsStore = {
471
+ data: loadSettings(),
472
+ listeners: [],
473
+ get() {
474
+ return this.data;
475
+ },
476
+ set(key, value) {
477
+ const next = {
478
+ ...this.data,
479
+ [key]: value
480
+ };
481
+ this.data = next;
482
+ try {
483
+ localStorage.setItem(SETTINGS_KEY, JSON.stringify(next));
484
+ } catch {}
485
+ for (const fn of this.listeners) try {
486
+ fn(next);
487
+ } catch {}
488
+ },
489
+ subscribe(fn) {
490
+ this.listeners.push(fn);
491
+ return () => {
492
+ this.listeners = this.listeners.filter((f) => f !== fn);
493
+ };
494
+ }
495
+ };
496
+ store.open = !!settingsStore.get().defaultOpen;
497
+ const useSettings = () => {
498
+ const [s, setS] = React.useState(settingsStore.get());
499
+ React.useEffect(() => settingsStore.subscribe(setS), []);
500
+ return s;
501
+ };
502
+ /** 订阅客户端会话库:工作区切换时自动返回新的会话 id(触发组件重新取数) */
503
+ function useSessionId() {
504
+ const [sessionId, setSessionId] = React.useState(currentSessionId());
505
+ React.useEffect(() => {
506
+ let list;
507
+ try {
508
+ list = ctx.get("sessions")?.list;
509
+ } catch {
510
+ list = void 0;
511
+ }
512
+ if (!list || typeof list.subscribe !== "function") return;
513
+ return list.subscribe(() => setSessionId(currentSessionId()));
514
+ }, []);
515
+ return sessionId;
516
+ }
517
+ //#endregion
518
+ //#region src/shared/highlight.js
519
+ /**
520
+ * 共享:零依赖语法高亮器(无模板字面量以外的构建期依赖,正则引擎全部内联)。
521
+ *
522
+ * 可移植性约束见 ext.ts 顶部说明:本文件经 `?raw` 原样内联进独立弹出页的
523
+ * 经典 <script>,因此只能使用 JSDoc 标注类型。输出 span.tok-* 令牌,颜色
524
+ * 由两端各自的 CSS(styles.ts / page 模板)负责。
525
+ */
526
+ /** @param {string} s @returns {string} */
527
+ function escHtml(s) {
528
+ return String(s).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
529
+ }
530
+ /**
531
+ * @param {[string, string][]} specs [令牌类名, 正则源] 列表,顺序即优先级
532
+ * @param {string} [flags]
533
+ * @returns {(code: string) => string}
534
+ */
535
+ function makeHl(specs, flags) {
536
+ let src = "";
537
+ for (let i = 0; i < specs.length; i += 1) {
538
+ const spec = specs[i];
539
+ if (!spec) continue;
540
+ src += (i ? "|" : "") + "(" + spec[1] + ")";
541
+ }
542
+ const re = new RegExp(src, flags || "g");
543
+ return (code) => {
544
+ re.lastIndex = 0;
545
+ let out = "";
546
+ let last = 0;
547
+ let m;
548
+ while ((m = re.exec(code)) !== null) {
549
+ if (m.index > last) out += escHtml(code.slice(last, m.index));
550
+ for (let g = 1; g < m.length; g += 1) {
551
+ const tok = m[g];
552
+ if (tok !== void 0) {
553
+ const spec = specs[g - 1];
554
+ if (spec) out += "<span class=\"tok-" + spec[0] + "\">" + escHtml(tok) + "</span>";
555
+ break;
556
+ }
557
+ }
558
+ last = re.lastIndex;
559
+ if (m[0].length === 0) {
560
+ re.lastIndex += 1;
561
+ last = re.lastIndex;
562
+ }
563
+ }
564
+ if (last < code.length) out += escHtml(code.slice(last));
565
+ return out;
566
+ };
567
+ }
568
+ const S_DQ = "\"(?:[^\"\\\\\\n]|\\\\.)*\"";
569
+ const S_SQ = "\\x27(?:[^\\x27\\\\\\n]|\\\\.)*\\x27";
570
+ const S_BT = "\\x60(?:[^\\x60\\\\]|\\\\.)*\\x60";
571
+ const NUM = "\\b(?:0[xX][0-9a-fA-F]+|\\d+(?:\\.\\d+)?(?:[eE][+-]?\\d+)?)\\b";
572
+ const C_LINE = "//[^\\n]*";
573
+ const C_BLK = "/\\*[\\s\\S]*?\\*/";
574
+ const HASH = "#[^\\n]*";
575
+ const HTML_COMMENT = "<!--[\\s\\S]*?-->";
576
+ const CSS_NUM = "\\b\\d+(?:\\.\\d+)?(?:[a-zA-Z%]*)\\b";
577
+ const HEX = "#[0-9a-fA-F]{3,8}\\b";
578
+ const AT = "@[\\w-]+";
579
+ const PROP = "[\\w-]+(?=\\s*:)";
580
+ const TAG = "</?[\\w-]+|/?>";
581
+ const ATTR = "[\\w-]+(?==)";
582
+ const VAR = "\\$(?:\\{[\\w]+\\}|[\\w]+)";
583
+ const VAR_PHP = "\\$\\w+";
584
+ const DECORATOR = "@[\\w.]+";
585
+ const IMPORTANT = "!important\\b";
586
+ const FUNC = "\\b[A-Za-z_$][\\w$]*(?=\\s*\\()";
587
+ const FUNC_PY = "\\b[A-Za-z_][\\w]*(?=\\s*\\()";
588
+ const CLASS = "\\b[A-Z][\\w$]*\\b";
589
+ const YAML_KEY = "^\\s*(?:-\\s+)?[\\w.@-]+(?=\\s*:)";
590
+ /** @param {string} kw 空白分隔的关键字列表 @returns {string} */
591
+ function kwWord(kw) {
592
+ return "\\b(?:" + kw.replace(/\s+/g, "|") + ")\\b";
593
+ }
594
+ const JS_KW = "break case catch class const continue debugger default delete do else export extends finally for function if import in instanceof let new return static super switch this throw try typeof var void while with yield async await of get set null undefined true false";
595
+ const PY_KW = "and as assert async await break class continue def del elif else except finally for from global if import in is lambda nonlocal not or pass raise return try while with yield True False None self";
596
+ const SH_KW = "if then elif else fi for while do done case esac function select in until return exit set unset export readonly local shift source";
597
+ const SQL_KW = "select from where insert into update delete create drop alter table index view join left right inner outer full on as and or not null group by order having limit offset union all distinct values set primary key foreign references default like between is in exists asc desc";
598
+ const C_KW = "auto break case const continue default do double else enum extern float for goto if int long register return short signed sizeof static struct switch typedef union unsigned void volatile while";
599
+ const GO_KW = "break case chan const continue default defer else fallthrough for func go goto if import interface map package range return select struct switch type var";
600
+ const RUST_KW = "as async await break const continue crate dyn else enum extern fn for if impl in let loop match mod move mut pub ref return self Self static struct super trait type union unsafe use where while";
601
+ const JAVA_KW = "abstract assert boolean break byte case catch char class const continue default do double else enum extends final finally float for if implements import instanceof int interface long native new package private protected public return short static strictfp super switch synchronized this throw throws transient try void volatile while";
602
+ const RB_KW = "begin case class def do else elsif end ensure for if module next nil not or redo rescue retry return self super then true false undef unless until when while yield";
603
+ const PHP_KW = "abstract and array as break callable case catch class clone const continue declare default do echo else elseif empty enddeclare endfor endforeach endif endswitch endwhile extends final finally fn for foreach function global if implements include instanceof insteadof interface isset list namespace new or print private protected public require return static switch throw trait try unset use var while xor yield";
604
+ /** @param {string} kw @returns {(code: string) => string} */
605
+ function cFamily(kw) {
606
+ return makeHl([
607
+ ["comment", C_LINE + "|" + C_BLK],
608
+ ["string", S_BT + "|" + S_DQ + "|" + S_SQ],
609
+ ["number", NUM],
610
+ ["keyword", kwWord(kw)],
611
+ ["function", FUNC],
612
+ ["class", CLASS]
613
+ ]);
614
+ }
615
+ /** @type {Record<string, (code: string) => string>} */
616
+ const HL_ENGINES = {
617
+ js: makeHl([
618
+ ["comment", C_LINE + "|" + C_BLK],
619
+ ["string", S_BT + "|" + S_DQ + "|" + S_SQ],
620
+ ["number", NUM],
621
+ ["keyword", kwWord(JS_KW)],
622
+ ["builtin", "\\b(?:console|Math|JSON|Promise|Array|Object|String|Number|Boolean|RegExp|Date|Map|Set|WeakMap|WeakSet|Symbol|BigInt|Infinity|NaN|window|document|process|require|module|exports|setTimeout|clearTimeout|fetch|globalThis)\\b"],
623
+ ["function", FUNC],
624
+ ["class", CLASS]
625
+ ]),
626
+ py: makeHl([
627
+ ["comment", HASH],
628
+ ["string", "(?:\"\"\"[\\s\\S]*?\"\"\"|\\x27\\x27\\x27[\\s\\S]*?\\x27\\x27\\x27)|(?:[rfbuRFBU]{0,2})(?:\"(?:[^\"\\\\\\n]|\\\\.)*\"|\\x27(?:[^\\x27\\\\\\n]|\\\\.)*\\x27)"],
629
+ ["number", NUM],
630
+ ["keyword", kwWord(PY_KW)],
631
+ ["builtin", "\\b(?:print|len|range|enumerate|zip|map|filter|int|str|float|bool|list|dict|set|tuple|type|isinstance|super|open|input|repr|format|sorted|reversed|sum|min|max|abs|round|any|all|next|iter|dir|vars|getattr|setattr|hasattr|id|hash|bytes|bytearray|complex|frozenset|object|classmethod|staticmethod|property|Exception|ValueError|TypeError|KeyError|IndexError|ImportError|RuntimeError|StopIteration)\\b"],
632
+ ["decorator", DECORATOR],
633
+ ["function", FUNC_PY]
634
+ ]),
635
+ css: makeHl([
636
+ ["comment", C_BLK],
637
+ ["string", S_DQ + "|" + S_SQ],
638
+ ["atrule", AT],
639
+ ["property", PROP],
640
+ ["number", CSS_NUM],
641
+ ["hex", HEX],
642
+ ["important", IMPORTANT]
643
+ ]),
644
+ html: makeHl([
645
+ ["comment", HTML_COMMENT],
646
+ ["string", S_DQ + "|" + S_SQ],
647
+ ["tag", TAG],
648
+ ["attr", ATTR]
649
+ ]),
650
+ sh: makeHl([
651
+ ["comment", HASH],
652
+ ["string", S_DQ + "|" + S_SQ + "|" + S_BT],
653
+ ["variable", VAR],
654
+ ["number", NUM],
655
+ ["keyword", kwWord(SH_KW)]
656
+ ]),
657
+ yaml: makeHl([
658
+ ["comment", HASH],
659
+ ["string", S_DQ + "|" + S_SQ],
660
+ ["number", NUM],
661
+ ["bool", "\\b(?:true|false|null|yes|no|on|off)\\b"],
662
+ ["key", YAML_KEY]
663
+ ], "gm"),
664
+ sql: makeHl([
665
+ ["comment", "--[^\\n]*|" + C_BLK],
666
+ ["string", S_SQ + "|" + S_DQ],
667
+ ["number", NUM],
668
+ ["keyword", kwWord(SQL_KW)],
669
+ ["function", FUNC_PY]
670
+ ], "gi"),
671
+ json: makeHl([
672
+ ["string", S_DQ],
673
+ ["number", NUM],
674
+ ["bool", "\\b(?:true|false|null)\\b"]
675
+ ]),
676
+ c: cFamily(C_KW),
677
+ cpp: cFamily(C_KW),
678
+ go: cFamily(GO_KW),
679
+ rust: cFamily(RUST_KW),
680
+ java: cFamily(JAVA_KW),
681
+ rb: makeHl([
682
+ ["comment", HASH],
683
+ ["string", S_DQ + "|" + S_SQ],
684
+ ["number", NUM],
685
+ ["keyword", kwWord(RB_KW)],
686
+ ["function", FUNC_PY],
687
+ ["class", CLASS]
688
+ ]),
689
+ php: makeHl([
690
+ ["comment", C_LINE + "|" + C_BLK + "|" + HASH],
691
+ ["string", S_DQ + "|" + S_SQ],
692
+ ["variable", VAR_PHP],
693
+ ["number", NUM],
694
+ ["keyword", kwWord(PHP_KW)],
695
+ ["function", FUNC_PY]
696
+ ])
697
+ };
698
+ /** @type {Record<string, string>} */
699
+ const HL_LANG_MAP = {
700
+ js: "js",
701
+ mjs: "js",
702
+ cjs: "js",
703
+ jsx: "js",
704
+ javascript: "js",
705
+ ts: "js",
706
+ tsx: "js",
707
+ mts: "js",
708
+ cts: "js",
709
+ typescript: "js",
710
+ json: "json",
711
+ jsonc: "json",
712
+ json5: "js",
713
+ py: "py",
714
+ python: "py",
715
+ pyw: "py",
716
+ rb: "rb",
717
+ ruby: "rb",
718
+ go: "go",
719
+ golang: "go",
720
+ rs: "rust",
721
+ rust: "rust",
722
+ java: "java",
723
+ c: "c",
724
+ h: "c",
725
+ cc: "cpp",
726
+ cpp: "cpp",
727
+ cxx: "cpp",
728
+ hpp: "cpp",
729
+ cs: "c",
730
+ csharp: "c",
731
+ kotlin: "c",
732
+ kt: "c",
733
+ swift: "c",
734
+ php: "php",
735
+ yaml: "yaml",
736
+ yml: "yaml",
737
+ toml: "sh",
738
+ ini: "sh",
739
+ conf: "sh",
740
+ properties: "sh",
741
+ env: "sh",
742
+ md: "md",
743
+ markdown: "md",
744
+ mdx: "md",
745
+ html: "html",
746
+ htm: "html",
747
+ xhtml: "html",
748
+ vue: "html",
749
+ xml: "html",
750
+ svg: "html",
751
+ css: "css",
752
+ scss: "css",
753
+ less: "css",
754
+ sql: "sql",
755
+ lua: "c",
756
+ sh: "sh",
757
+ bash: "sh",
758
+ shell: "sh",
759
+ zsh: "sh",
760
+ fish: "sh"
761
+ };
762
+ /** @param {string} hint 扩展名或语言名 @returns {string} 引擎键名 */
763
+ function hlLangOf(hint) {
764
+ let h = String(hint || "").toLowerCase();
765
+ if (h.charAt(0) === ".") h = h.slice(1);
766
+ return HL_LANG_MAP[h] || "plain";
767
+ }
768
+ /** @param {string} src @param {string} hint @returns {string} HTML */
769
+ function highlightCode(src, hint) {
770
+ const fn = HL_ENGINES[hlLangOf(hint)];
771
+ return fn ? fn(String(src)) : escHtml(src);
772
+ }
773
+ //#endregion
774
+ //#region src/shared/markdown.js
775
+ /**
776
+ * 共享:极简 Markdown → HTML 渲染器。
777
+ *
778
+ * 可移植性约束见 ext.ts 顶部说明:本文件经 `?raw` 原样内联进独立弹出页的
779
+ * 经典 <script>,只能使用 JSDoc 标注类型。围栏代码块通过 shared/highlight
780
+ * 的 highlightCode 高亮。
781
+ */
782
+ /** @param {string} s @returns {string} */
783
+ function mdEscape(s) {
784
+ return String(s).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
785
+ }
786
+ /** @param {string} s @returns {string} */
787
+ function mdInline(s) {
788
+ s = s.replace(/`([^`]+)`/g, (m, c) => "<code>" + c + "</code>");
789
+ s = s.replace(/!\[([^\]]*)\]\(([^)\s]+)\)/g, "<img alt=\"$1\" src=\"$2\">");
790
+ s = s.replace(/\[([^\]]+)\]\(([^)\s]+)\)/g, "<a href=\"$2\" target=\"_blank\" rel=\"noopener noreferrer\">$1</a>");
791
+ s = s.replace(/\*\*([^*]+)\*\*/g, "<strong>$1</strong>");
792
+ s = s.replace(/\*([^*\n]+)\*/g, "<em>$1</em>");
793
+ return s;
794
+ }
795
+ /** @param {string} src @returns {string} HTML */
796
+ function mdToHtml(src) {
797
+ const lines = String(src || "").replace(/\r\n/g, "\n").split("\n");
798
+ /** @type {string[]} */
799
+ const out = [];
800
+ let i = 0;
801
+ while (i < lines.length) {
802
+ const line = lines[i];
803
+ if (/^\s*```/.test(line)) {
804
+ const fence = /^\s*```([\w+-]*)/.exec(line);
805
+ const langHint = fence ? fence[1] || "" : "";
806
+ /** @type {string[]} */
807
+ const buf = [];
808
+ i += 1;
809
+ while (i < lines.length && !/^\s*```/.test(lines[i])) {
810
+ buf.push(lines[i]);
811
+ i += 1;
812
+ }
813
+ i += 1;
814
+ out.push("<pre><code>" + highlightCode(buf.join("\n"), langHint) + "</code></pre>");
815
+ continue;
816
+ }
817
+ const h = /^(#{1,6})\s+(.*)$/.exec(line);
818
+ if (h) {
819
+ const lv = (h[1] || "").length;
820
+ out.push("<h" + lv + ">" + mdInline(mdEscape(h[2])) + "</h" + lv + ">");
821
+ i += 1;
822
+ continue;
823
+ }
824
+ if (/^\s*(---+|\*\*\*+|___+)\s*$/.test(line)) {
825
+ out.push("<hr>");
826
+ i += 1;
827
+ continue;
828
+ }
829
+ if (/^\s*>\s?/.test(line)) {
830
+ /** @type {string[]} */
831
+ const q = [];
832
+ while (i < lines.length && /^\s*>\s?/.test(lines[i])) {
833
+ q.push(lines[i].replace(/^\s*>\s?/, ""));
834
+ i += 1;
835
+ }
836
+ out.push("<blockquote>" + mdInline(mdEscape(q.join(" "))) + "</blockquote>");
837
+ continue;
838
+ }
839
+ if (/^\s*[-*+]\s+/.test(line)) {
840
+ /** @type {string[]} */
841
+ const lis = [];
842
+ while (i < lines.length && /^\s*[-*+]\s+/.test(lines[i])) {
843
+ lis.push(mdInline(mdEscape(lines[i].replace(/^\s*[-*+]\s+/, ""))));
844
+ i += 1;
845
+ }
846
+ out.push("<ul>" + lis.map((x) => "<li>" + x + "</li>").join("") + "</ul>");
847
+ continue;
848
+ }
849
+ if (/^\s*\d+\.\s+/.test(line)) {
850
+ /** @type {string[]} */
851
+ const lis2 = [];
852
+ while (i < lines.length && /^\s*\d+\.\s+/.test(lines[i])) {
853
+ lis2.push(mdInline(mdEscape(lines[i].replace(/^\s*\d+\.\s+/, ""))));
854
+ i += 1;
855
+ }
856
+ out.push("<ol>" + lis2.map((x) => "<li>" + x + "</li>").join("") + "</ol>");
857
+ continue;
858
+ }
859
+ if (line.trim() === "") {
860
+ i += 1;
861
+ continue;
862
+ }
863
+ out.push("<p>" + mdInline(mdEscape(line)) + "</p>");
864
+ i += 1;
865
+ }
866
+ return out.join("\n");
867
+ }
868
+ //#endregion
869
+ //#region src/client/preview.tsx
870
+ /**
871
+ * Client 侧:预览渲染 —— 代码视图(行号 + 语法高亮)、git diff 视图、
872
+ * 自定义 pdf.js 渲染器、Markdown / 图片 / HTML 沙箱、多类型分发。
873
+ */
874
+ function renderDiff(diff) {
875
+ return /* @__PURE__ */ h("div", { className: "artifacts-diff" }, diff && diff.before != null && diff.before !== "" ? /* @__PURE__ */ h("div", { className: "artifacts-diff-block artifacts-diff-del" }, /* @__PURE__ */ h("div", { className: "artifacts-diff-label" }, "- 删除"), /* @__PURE__ */ h("pre", { className: "artifacts-diff-pre" }, diff.before)) : null, /* @__PURE__ */ h("div", { className: "artifacts-diff-block artifacts-diff-add" }, /* @__PURE__ */ h("div", { className: "artifacts-diff-label" }, "+ 新增"), /* @__PURE__ */ h("pre", { className: "artifacts-diff-pre" }, diff && diff.after != null ? diff.after : "")));
876
+ }
877
+ /** 代码预览:行号栏 + 语法高亮代码(共享高亮器,语言取自扩展名) */
878
+ function CodeView({ code, path }) {
879
+ const src = String(code || "");
880
+ return /* @__PURE__ */ h("div", { className: "artifacts-code" }, /* @__PURE__ */ h("div", { className: "artifacts-code-scroll" }, /* @__PURE__ */ h("pre", {
881
+ className: "artifacts-code-gutter",
882
+ "aria-hidden": "true"
883
+ }, src.replace(/\n$/, "").split("\n").map((_, i) => String(i + 1)).join("\n")), /* @__PURE__ */ h("pre", { className: "artifacts-code-pre" }, /* @__PURE__ */ h("code", { dangerouslySetInnerHTML: { __html: highlightCode(src, fileExt(path || "")) } }))));
884
+ }
885
+ let pdfjsPromise = null;
886
+ function loadPdfjs() {
887
+ if (typeof window === "undefined") return Promise.reject(/* @__PURE__ */ new Error("no window"));
888
+ if (window.pdfjsLib) {
889
+ try {
890
+ window.pdfjsLib.GlobalWorkerOptions.workerSrc = "/flyout-sidebar/pdfjs/pdf.worker.min.js";
891
+ } catch {}
892
+ return Promise.resolve(window.pdfjsLib);
893
+ }
894
+ if (pdfjsPromise) return pdfjsPromise;
895
+ pdfjsPromise = new Promise((resolve, reject) => {
896
+ const s = document.createElement("script");
897
+ s.src = "/flyout-sidebar/pdfjs/pdf.min.js";
898
+ s.async = true;
899
+ s.onload = () => {
900
+ try {
901
+ if (!window.pdfjsLib) throw new Error("pdf.js 加载失败");
902
+ window.pdfjsLib.GlobalWorkerOptions.workerSrc = "/flyout-sidebar/pdfjs/pdf.worker.min.js";
903
+ resolve(window.pdfjsLib);
904
+ } catch (e) {
905
+ reject(e);
906
+ }
907
+ };
908
+ s.onerror = () => {
909
+ pdfjsPromise = null;
910
+ reject(/* @__PURE__ */ new Error("pdf.js 加载失败"));
911
+ };
912
+ document.head.appendChild(s);
913
+ });
914
+ return pdfjsPromise;
915
+ }
916
+ function PdfView({ path }) {
917
+ const [phase, setPhase] = React.useState("loading");
918
+ const [error, setError] = React.useState(null);
919
+ const [pageCount, setPageCount] = React.useState(0);
920
+ const [pageNo, setPageNo] = React.useState(1);
921
+ const [zoom, setZoom] = React.useState(1);
922
+ const [fitScale, setFitScale] = React.useState(null);
923
+ const scrollRef = React.useRef(null);
924
+ const canvasRef = React.useRef(null);
925
+ const docRef = React.useRef(null);
926
+ const taskRef = React.useRef(null);
927
+ React.useEffect(() => {
928
+ let alive = true;
929
+ setPhase("loading");
930
+ setError(null);
931
+ setPageCount(0);
932
+ setPageNo(1);
933
+ setZoom(1);
934
+ setFitScale(null);
935
+ loadPdfjs().then((lib) => {
936
+ const url = "/flyout-sidebar/media?path=" + encodeURIComponent(path);
937
+ return lib.getDocument({ url }).promise;
938
+ }).then((doc) => {
939
+ if (!alive) {
940
+ try {
941
+ doc.destroy();
942
+ } catch {}
943
+ return;
944
+ }
945
+ docRef.current = doc;
946
+ setPageCount(doc.numPages || 0);
947
+ setPhase("ready");
948
+ }).catch((e) => {
949
+ if (alive) {
950
+ setError(e instanceof Error && e.message ? e.message : String(e));
951
+ setPhase("error");
952
+ }
953
+ });
954
+ return () => {
955
+ alive = false;
956
+ if (taskRef.current) try {
957
+ taskRef.current.cancel();
958
+ } catch {}
959
+ if (docRef.current) {
960
+ try {
961
+ docRef.current.destroy();
962
+ } catch {}
963
+ docRef.current = null;
964
+ }
965
+ };
966
+ }, [path]);
967
+ React.useEffect(() => {
968
+ if (phase !== "ready" || fitScale != null) return;
969
+ const scroll = scrollRef.current;
970
+ const doc = docRef.current;
971
+ if (!scroll || !doc) return;
972
+ let w = scroll.clientWidth;
973
+ if (typeof window.getComputedStyle === "function") try {
974
+ const cs = window.getComputedStyle(scroll);
975
+ w -= (parseFloat(cs.paddingLeft) || 0) + (parseFloat(cs.paddingRight) || 0);
976
+ } catch {}
977
+ if (!w) return;
978
+ doc.getPage(1).then((pageObj) => {
979
+ const vp = pageObj.getViewport({ scale: 1 });
980
+ if (vp && vp.width > 0) setFitScale(w / vp.width);
981
+ }).catch(() => {});
982
+ }, [phase, fitScale]);
983
+ React.useEffect(() => {
984
+ if (phase !== "ready" || fitScale == null) return;
985
+ const canvas = canvasRef.current;
986
+ const doc = docRef.current;
987
+ if (!canvas || !doc) return;
988
+ let alive = true;
989
+ doc.getPage(pageNo).then((pageObj) => {
990
+ if (!alive) return;
991
+ const scale = fitScale * zoom;
992
+ const viewport = pageObj.getViewport({ scale });
993
+ const dpr = typeof window !== "undefined" && window.devicePixelRatio || 1;
994
+ canvas.width = Math.floor(viewport.width * dpr);
995
+ canvas.height = Math.floor(viewport.height * dpr);
996
+ canvas.style.width = Math.floor(viewport.width) + "px";
997
+ canvas.style.height = Math.floor(viewport.height) + "px";
998
+ const ctx2d = canvas.getContext("2d");
999
+ if (!ctx2d) return;
1000
+ if (taskRef.current) try {
1001
+ taskRef.current.cancel();
1002
+ } catch {}
1003
+ taskRef.current = pageObj.render({
1004
+ canvasContext: ctx2d,
1005
+ viewport,
1006
+ transform: dpr !== 1 ? [
1007
+ dpr,
1008
+ 0,
1009
+ 0,
1010
+ dpr,
1011
+ 0,
1012
+ 0
1013
+ ] : null
1014
+ });
1015
+ }).catch(() => {});
1016
+ return () => {
1017
+ alive = false;
1018
+ };
1019
+ }, [
1020
+ phase,
1021
+ pageNo,
1022
+ zoom,
1023
+ fitScale
1024
+ ]);
1025
+ const clampPage = (n) => Math.max(1, Math.min(pageCount || 1, n));
1026
+ const goPage = (n) => setPageNo(clampPage(n));
1027
+ const zoomBy = (f) => setZoom((z) => Math.max(.25, Math.min(4, Math.round(z * f * 100) / 100)));
1028
+ if (phase === "loading") return /* @__PURE__ */ h("div", { className: "artifacts-pdfview" }, /* @__PURE__ */ h("div", { className: "artifacts-hint" }, "加载 PDF…"));
1029
+ if (phase === "error") return /* @__PURE__ */ h("embed", {
1030
+ className: "artifacts-pdf",
1031
+ src: "/flyout-sidebar/media?path=" + encodeURIComponent(path),
1032
+ type: "application/pdf",
1033
+ title: path
1034
+ });
1035
+ const disabled = pageCount <= 0;
1036
+ return /* @__PURE__ */ h("div", { className: "artifacts-pdfview" }, /* @__PURE__ */ h("div", { className: "artifacts-pdfview-bar" }, /* @__PURE__ */ h("button", {
1037
+ type: "button",
1038
+ className: "artifacts-pdfview-btn",
1039
+ title: "缩小",
1040
+ disabled,
1041
+ onClick: () => zoomBy(.8)
1042
+ }, "−"), /* @__PURE__ */ h("span", { className: "artifacts-pdfview-zoom" }, Math.round(zoom * 100) + "%"), /* @__PURE__ */ h("button", {
1043
+ type: "button",
1044
+ className: "artifacts-pdfview-btn",
1045
+ title: "放大",
1046
+ disabled,
1047
+ onClick: () => zoomBy(1.25)
1048
+ }, "+"), /* @__PURE__ */ h("span", { className: "artifacts-pdfview-spacer" }), /* @__PURE__ */ h("button", {
1049
+ type: "button",
1050
+ className: "artifacts-pdfview-btn",
1051
+ title: "上一页",
1052
+ disabled: disabled || pageNo <= 1,
1053
+ onClick: () => goPage(pageNo - 1)
1054
+ }, "‹"), /* @__PURE__ */ h("span", { className: "artifacts-pdfview-page" }, pageNo + " / " + pageCount), /* @__PURE__ */ h("button", {
1055
+ type: "button",
1056
+ className: "artifacts-pdfview-btn",
1057
+ title: "下一页",
1058
+ disabled: disabled || pageNo >= pageCount,
1059
+ onClick: () => goPage(pageNo + 1)
1060
+ }, "›")), /* @__PURE__ */ h("div", {
1061
+ className: "artifacts-pdfview-scroll",
1062
+ ref: scrollRef
1063
+ }, /* @__PURE__ */ h("canvas", {
1064
+ ref: canvasRef,
1065
+ className: "artifacts-pdfview-canvas"
1066
+ })));
1067
+ }
1068
+ /** 统一 git diff 渲染:meta/hunk/+/- 行着色,等宽可滚动 */
1069
+ function GitDiffView({ diff }) {
1070
+ const text = String(diff || "");
1071
+ if (!text) return /* @__PURE__ */ h("div", { className: "artifacts-hint" }, "没有未提交的变更(相对于 HEAD)");
1072
+ return /* @__PURE__ */ h("div", { className: "artifacts-gitdiff" }, text.replace(/\n$/, "").split("\n").map((line, i) => {
1073
+ let cls = "gd-line";
1074
+ if (line.startsWith("@@")) cls += " gd-hunk";
1075
+ else if (line.startsWith("+") && !line.startsWith("+++")) cls += " gd-add";
1076
+ else if (line.startsWith("-") && !line.startsWith("---")) cls += " gd-del";
1077
+ else if (line.startsWith("diff ") || line.startsWith("index ") || line.startsWith("--- ") || line.startsWith("+++ ") || line.startsWith("new file") || line.startsWith("deleted file") || line.startsWith("old mode") || line.startsWith("new mode") || line.startsWith("rename ") || line.startsWith("similarity ") || line.startsWith("copy ") || line.startsWith("Binary files") || line.startsWith("\\")) cls += " gd-meta";
1078
+ return /* @__PURE__ */ h("div", {
1079
+ key: i,
1080
+ className: cls
1081
+ }, line);
1082
+ }));
1083
+ }
1084
+ function renderPreview(p) {
1085
+ if (p.loading) return /* @__PURE__ */ h("div", { className: "artifacts-hint" }, "加载中…");
1086
+ if (p.ok === false) return /* @__PURE__ */ h("div", { className: "artifacts-error" }, p.error || "读取失败");
1087
+ if (p.git) return /* @__PURE__ */ h("div", { className: "artifacts-preview-body" }, /* @__PURE__ */ h(GitDiffView, { diff: p.diff }));
1088
+ const type = p.type || extType(p.path);
1089
+ let view;
1090
+ if (type === "image") view = /* @__PURE__ */ h("img", {
1091
+ className: "artifacts-img",
1092
+ src: "/flyout-sidebar/media?path=" + encodeURIComponent(p.path || ""),
1093
+ alt: p.path || ""
1094
+ });
1095
+ else if (type === "html") view = /* @__PURE__ */ h("iframe", {
1096
+ className: "artifacts-iframe",
1097
+ sandbox: "allow-scripts",
1098
+ srcDoc: p.content || "",
1099
+ title: p.path || ""
1100
+ });
1101
+ else if (type === "pdf") view = /* @__PURE__ */ h(PdfView, { path: p.path || "" });
1102
+ else if (type === "markdown") view = /* @__PURE__ */ h("div", {
1103
+ className: "artifacts-markdown",
1104
+ dangerouslySetInnerHTML: { __html: mdToHtml(p.content || "") }
1105
+ });
1106
+ else view = /* @__PURE__ */ h(Fragment, null, /* @__PURE__ */ h(CodeView, {
1107
+ code: p.content,
1108
+ path: p.path
1109
+ }), p.truncated ? /* @__PURE__ */ h("div", { className: "artifacts-diff-label" }, "(truncated preview)") : null);
1110
+ return /* @__PURE__ */ h("div", { className: "artifacts-preview-body" }, p.diff && typeof p.diff === "object" ? renderDiff(p.diff) : null, view);
1111
+ }
1112
+ //#endregion
1113
+ //#region src/client/icons.tsx
1114
+ /**
1115
+ * Client 侧:内联 SVG 图标(复刻 DSH primitives 图标风格,currentColor 跟随
1116
+ * 主题)。classic JSX 经 h 工厂编译。
1117
+ */
1118
+ /** 面板图标:圆角矩形 + 右侧分隔线(主侧边栏开关的镜像) */
1119
+ const PanelIcon = ({ size }) => /* @__PURE__ */ h("svg", {
1120
+ width: size,
1121
+ height: size,
1122
+ viewBox: "0 0 16 16",
1123
+ fill: "none",
1124
+ "aria-hidden": "true"
1125
+ }, /* @__PURE__ */ h("rect", {
1126
+ x: 1.5,
1127
+ y: 1.5,
1128
+ width: 13,
1129
+ height: 13,
1130
+ rx: 2.8,
1131
+ stroke: "currentColor",
1132
+ strokeWidth: 1.5
1133
+ }), /* @__PURE__ */ h("line", {
1134
+ x1: 10.2,
1135
+ y1: 2.6,
1136
+ x2: 10.2,
1137
+ y2: 13.4,
1138
+ stroke: "currentColor",
1139
+ strokeWidth: 1.5
1140
+ }));
1141
+ const FolderClosedIcon = ({ size }) => /* @__PURE__ */ h("svg", {
1142
+ width: size,
1143
+ height: size,
1144
+ viewBox: "0 0 16 16",
1145
+ fill: "none",
1146
+ "aria-hidden": "true"
1147
+ }, /* @__PURE__ */ h("path", {
1148
+ transform: "translate(1.5 2.429)",
1149
+ d: "M5.05582 0.518756L4.50669 0.86654L5.05582 0.518756ZM13 9.4837L13.65 9.4837L13.65 3.53962L13 3.53962L12.35 3.53962L12.35 9.4837L13 9.4837ZM11.3264 1.86603L11.3264 1.21603L6.52313 1.21603L6.52313 1.86603L6.52313 2.51603L11.3264 2.51603L11.3264 1.86603ZM5.58054 1.34727L6.12968 0.999489L5.60495 0.170972L5.05582 0.518756L4.50669 0.86654L5.03141 1.69506L5.58054 1.34727ZM4.11323 1.23058e-13L4.11323 -0.65L1.67359 -0.65L1.67359 5.00699e-14L1.67359 0.65L4.11323 0.65L4.11323 1.23058e-13ZM0 1.67359L-0.65 1.67359L-0.65 9.4837L0 9.4837L0.65 9.4837L0.65 1.67359L0 1.67359ZM11.3264 11.1573L11.3264 10.5073L1.67359 10.5073L1.67359 11.1573L1.67359 11.8073L11.3264 11.8073L11.3264 11.1573ZM0 9.4837L-0.65 9.4837C-0.65 10.767 0.390308 11.8073 1.67359 11.8073L1.67359 11.1573L1.67359 10.5073C1.10828 10.5073 0.65 10.049 0.65 9.4837L0 9.4837ZM1.67359 5.00699e-14L1.67359 -0.65C0.390307 -0.65 -0.65 0.390309 -0.65 1.67359L0 1.67359L0.65 1.67359C0.65 1.10828 1.10828 0.65 1.67359 0.65L1.67359 5.00699e-14ZM5.05582 0.518756L5.60495 0.170972C5.28121 -0.340193 4.71829 -0.65 4.11323 -0.65L4.11323 1.23058e-13L4.11323 0.65C4.27282 0.65 4.4213 0.731715 4.50669 0.86654L5.05582 0.518756ZM6.52313 1.86603L6.52313 1.21603C6.36354 1.21603 6.21507 1.13431 6.12968 0.999489L5.58054 1.34727L5.03141 1.69506C5.35515 2.20622 5.91808 2.51603 6.52313 2.51603L6.52313 1.86603ZM13 3.53962L13.65 3.53962C13.65 2.25634 12.6097 1.21603 11.3264 1.21603L11.3264 1.86603L11.3264 2.51603C11.8917 2.51603 12.35 2.97431 12.35 3.53962L13 3.53962ZM13 9.4837L12.35 9.4837C12.35 10.049 11.8917 10.5073 11.3264 10.5073L11.3264 11.1573L11.3264 11.8073C12.6097 11.8073 13.65 10.767 13.65 9.4837L13 9.4837Z",
1150
+ fill: "currentColor"
1151
+ }));
1152
+ const FolderOpenIcon = ({ size }) => /* @__PURE__ */ h("svg", {
1153
+ width: size,
1154
+ height: size,
1155
+ viewBox: "0 0 16 16",
1156
+ fill: "none",
1157
+ "aria-hidden": "true"
1158
+ }, /* @__PURE__ */ h("path", {
1159
+ d: "M5.19629 1.57104C5.81144 1.5711 6.38623 1.8786 6.72754 2.39038L7.19922 3.09839C7.28454 3.22635 7.42824 3.30344 7.58203 3.30347H12.1699C13.5039 3.30348 14.5859 4.38548 14.5859 5.71948V6.62671C15.2694 7.02689 15.6605 7.85012 15.4385 8.68726L14.3848 12.658C14.1037 13.7164 13.1449 14.4527 12.0498 14.4529H2.91699C1.51651 14.4529 0.451662 13.2814 0.501954 11.9519V3.98706C0.501954 2.65305 1.58396 1.57104 2.91797 1.57104H5.19629ZM3.7793 7.75562C3.30994 7.75562 2.89883 8.07153 2.77832 8.52515L1.91602 11.7722C1.74167 12.4291 2.23734 13.073 2.91699 13.073H12.0498C12.5191 13.0728 12.9304 12.757 13.0508 12.3035L14.1045 8.33374C14.1819 8.04202 13.9619 7.756 13.6602 7.75562H3.7793ZM2.91797 2.9519C2.34625 2.9519 1.88281 3.41534 1.88281 3.98706V7.2937C2.33068 6.7269 3.02249 6.37476 3.7793 6.37476H13.2051V5.71948C13.2051 5.14777 12.7416 4.68434 12.1699 4.68433H7.58203C6.96675 4.6843 6.39209 4.37595 6.05078 3.86401L5.5791 3.15601C5.49379 3.02821 5.34995 2.95196 5.19629 2.9519H2.91797Z",
1160
+ fill: "currentColor"
1161
+ }), /* @__PURE__ */ h("path", {
1162
+ opacity: .2,
1163
+ d: "M13.6602 7.75525C13.9618 7.7556 14.1815 8.04179 14.1045 8.33337L13.0508 12.3031C12.9304 12.7567 12.5191 13.0725 12.0498 13.0726H2.91701C2.23744 13.0725 1.7417 12.4287 1.91603 11.7719L2.77834 8.52478C2.89898 8.07146 3.31018 7.75532 3.77931 7.75525H13.6602ZM5.1963 2.95154C5.34985 2.95159 5.49377 3.02803 5.57912 3.15564L6.0508 3.86365C6.39205 4.37553 6.96685 4.68385 7.58205 4.68396H12.1699C12.7416 4.68396 13.2049 5.14754 13.2051 5.71912V6.37439H3.77931C3.02267 6.37444 2.33067 6.72671 1.88283 7.29333V3.98669C1.88299 3.4152 2.34649 2.95168 2.91798 2.95154H5.1963Z",
1164
+ fill: "currentColor"
1165
+ }));
1166
+ const FileCodeIcon = ({ size }) => /* @__PURE__ */ h("svg", {
1167
+ width: size,
1168
+ height: size,
1169
+ viewBox: "0 0 16 16",
1170
+ fill: "none",
1171
+ "aria-hidden": "true"
1172
+ }, /* @__PURE__ */ h("path", {
1173
+ fillRule: "evenodd",
1174
+ clipRule: "evenodd",
1175
+ d: "M12.3368 1.53569L11.931 4.43172H14.8086V5.79673H11.7404L11.1962 9.67859H14.2839V11.0436H11.0056L10.4994 14.6529L9.14873 14.4643L9.62731 11.0436H5.75876L5.25252 14.6529L3.90186 14.4643L4.38043 11.0436H1.69141V9.67859H4.57104L5.11417 5.79673H2.21609V4.43172H5.30581L5.73724 1.34713L7.08995 1.53569L6.68414 4.43172H10.5527L10.9841 1.34713L12.3368 1.53569ZM5.94937 9.67859H9.81791L10.361 5.79673H6.49353L5.94937 9.67859Z",
1176
+ fill: "currentColor"
1177
+ }));
1178
+ const RefreshIcon = ({ size }) => /* @__PURE__ */ h("svg", {
1179
+ width: size,
1180
+ height: size,
1181
+ viewBox: "0 0 16 16",
1182
+ fill: "none",
1183
+ "aria-hidden": "true"
1184
+ }, /* @__PURE__ */ h("path", {
1185
+ d: "M7.92136 0.349152C10.3744 0.349234 12.5564 1.5052 13.9557 3.29894L15.1281 2.12759C15.3303 1.92546 15.6767 2.06943 15.6767 2.35538V5.53923C15.6766 5.71626 15.5329 5.85976 15.3559 5.86002H12.171C11.8854 5.8597 11.7426 5.51465 11.9443 5.31249L12.9641 4.29056C11.8237 2.74305 9.98908 1.74106 7.92136 1.74097C4.46436 1.74097 1.66233 4.543 1.66233 8C1.66233 11.457 4.46436 14.259 7.92136 14.259C11.3782 14.2589 14.1804 11.4569 14.1804 8H15.5722C15.5722 12.2251 12.1465 15.6507 7.92136 15.6508C3.69614 15.6508 0.270508 12.2252 0.270508 8C0.270508 3.77478 3.69614 0.349152 7.92136 0.349152Z",
1186
+ fill: "currentColor"
1187
+ }));
1188
+ /** 弹出(↗)箭头:新标签页打开链接 */
1189
+ const FlyoutIcon = ({ size }) => /* @__PURE__ */ h("svg", {
1190
+ width: size,
1191
+ height: size,
1192
+ viewBox: "0 0 16 16",
1193
+ fill: "none",
1194
+ "aria-hidden": "true"
1195
+ }, /* @__PURE__ */ h("path", {
1196
+ d: "M3.5 12.5 L12.5 3.5 M6.2 3.5 H12.5 V9.8",
1197
+ stroke: "currentColor",
1198
+ strokeWidth: 1.5,
1199
+ strokeLinecap: "round",
1200
+ strokeLinejoin: "round",
1201
+ fill: "none"
1202
+ }));
1203
+ /** ⇥ 面板收起:箭头推入竖线(朝侧边栏);预览隐藏时镜像(⇤)表示拉回 */
1204
+ const PanelCollapseIcon = ({ size }) => /* @__PURE__ */ h("svg", {
1205
+ width: size,
1206
+ height: size,
1207
+ viewBox: "0 0 16 16",
1208
+ fill: "none",
1209
+ "aria-hidden": "true"
1210
+ }, /* @__PURE__ */ h("line", {
1211
+ x1: 12.5,
1212
+ y1: 3,
1213
+ x2: 12.5,
1214
+ y2: 13,
1215
+ stroke: "currentColor",
1216
+ strokeWidth: 1.5,
1217
+ strokeLinecap: "round"
1218
+ }), /* @__PURE__ */ h("path", {
1219
+ d: "M3 8 H10 M7.8 5.6 L10.2 8 L7.8 10.4",
1220
+ stroke: "currentColor",
1221
+ strokeWidth: 1.5,
1222
+ strokeLinecap: "round",
1223
+ strokeLinejoin: "round",
1224
+ fill: "none"
1225
+ }));
1226
+ /** Git 变更(git 变更):经典 git-branch 图形,用于文件树 ⇄ 变更列表切换 */
1227
+ const GitBranchIcon = ({ size }) => /* @__PURE__ */ h("svg", {
1228
+ width: size,
1229
+ height: size,
1230
+ viewBox: "0 0 16 16",
1231
+ fill: "none",
1232
+ "aria-hidden": "true"
1233
+ }, /* @__PURE__ */ h("path", {
1234
+ d: "M4.5 4.6v6.8",
1235
+ stroke: "currentColor",
1236
+ strokeWidth: 1.4,
1237
+ strokeLinecap: "round",
1238
+ fill: "none"
1239
+ }), /* @__PURE__ */ h("circle", {
1240
+ cx: 4.5,
1241
+ cy: 3,
1242
+ r: 1.7,
1243
+ stroke: "currentColor",
1244
+ strokeWidth: 1.4,
1245
+ fill: "none"
1246
+ }), /* @__PURE__ */ h("circle", {
1247
+ cx: 4.5,
1248
+ cy: 13,
1249
+ r: 1.7,
1250
+ stroke: "currentColor",
1251
+ strokeWidth: 1.4,
1252
+ fill: "none"
1253
+ }), /* @__PURE__ */ h("circle", {
1254
+ cx: 11.5,
1255
+ cy: 3,
1256
+ r: 1.7,
1257
+ stroke: "currentColor",
1258
+ strokeWidth: 1.4,
1259
+ fill: "none"
1260
+ }), /* @__PURE__ */ h("path", {
1261
+ d: "M11.5 4.7v1.1c0 1.9-1.6 3.1-3.6 3.1-1.9 0-3.4 1.2-3.4 1.2",
1262
+ stroke: "currentColor",
1263
+ strokeWidth: 1.4,
1264
+ strokeLinecap: "round",
1265
+ fill: "none"
1266
+ }));
1267
+ //#endregion
1268
+ //#region src/client/components.tsx
1269
+ /**
1270
+ * Client 侧:UI 组件 —— 文件树、多标签预览侧边面板、角落触发按钮、设置区。
1271
+ */
1272
+ function FileTree({ onOpen, selectedPath, refreshToken }) {
1273
+ const [root, setRoot] = React.useState(null);
1274
+ const [children, setChildren] = React.useState({});
1275
+ const [expanded, setExpanded] = React.useState({});
1276
+ const [copiedPath, setCopiedPath] = React.useState(null);
1277
+ const [copiedLabel, setCopiedLabel] = React.useState("");
1278
+ const copyTimer = React.useRef(null);
1279
+ const rootTimer = React.useRef(null);
1280
+ const sessionId = useSessionId();
1281
+ const loadRoot = () => {
1282
+ setChildren({});
1283
+ setExpanded({});
1284
+ setRoot(null);
1285
+ if (rootTimer.current) clearTimeout(rootTimer.current);
1286
+ const attempt = (tries) => {
1287
+ host.listDir("", currentSessionId()).then((res) => {
1288
+ if (res && res.ok) setRoot({
1289
+ path: res.path || "",
1290
+ entries: res.entries || []
1291
+ });
1292
+ else if (tries > 0) rootTimer.current = setTimeout(() => attempt(tries - 1), 400);
1293
+ }).catch(() => {
1294
+ if (tries > 0) rootTimer.current = setTimeout(() => attempt(tries - 1), 400);
1295
+ });
1296
+ };
1297
+ attempt(3);
1298
+ };
1299
+ React.useEffect(() => {
1300
+ loadRoot();
1301
+ }, [sessionId, refreshToken]);
1302
+ React.useEffect(() => () => {
1303
+ if (rootTimer.current) clearTimeout(rootTimer.current);
1304
+ }, []);
1305
+ const toggle = (path) => {
1306
+ const nextExpanded = {
1307
+ ...expanded,
1308
+ [path]: !expanded[path]
1309
+ };
1310
+ setExpanded(nextExpanded);
1311
+ if (nextExpanded[path] && !children[path]) {
1312
+ setChildren({
1313
+ ...children,
1314
+ [path]: { loading: true }
1315
+ });
1316
+ host.listDir(path, currentSessionId()).then((res) => {
1317
+ setChildren((prev) => ({
1318
+ ...prev,
1319
+ [path]: res && res.ok ? { entries: res.entries || [] } : { error: res && res.error || "读取失败" }
1320
+ }));
1321
+ }).catch(() => {
1322
+ setChildren((prev) => ({
1323
+ ...prev,
1324
+ [path]: { error: "读取失败" }
1325
+ }));
1326
+ });
1327
+ }
1328
+ };
1329
+ const copyRef = (path) => {
1330
+ const text = "@" + path;
1331
+ let label = "已复制";
1332
+ const done = () => {
1333
+ setCopiedPath(path);
1334
+ setCopiedLabel(label);
1335
+ if (copyTimer.current) clearTimeout(copyTimer.current);
1336
+ copyTimer.current = setTimeout(() => {
1337
+ setCopiedPath(null);
1338
+ setCopiedLabel("");
1339
+ }, 1600);
1340
+ };
1341
+ if (quoteToComposer(path)) {
1342
+ label = "已插入输入框";
1343
+ done();
1344
+ return;
1345
+ }
1346
+ if (navigator.clipboard && navigator.clipboard.writeText) navigator.clipboard.writeText(text).then(done, () => {
1347
+ fallbackCopy(text);
1348
+ done();
1349
+ });
1350
+ else {
1351
+ fallbackCopy(text);
1352
+ done();
1353
+ }
1354
+ };
1355
+ const rowActions = (entry) => copiedPath === entry.path ? /* @__PURE__ */ h("span", { className: "artifacts-tree-copied" }, copiedLabel || "已复制") : /* @__PURE__ */ h("button", {
1356
+ type: "button",
1357
+ className: "artifacts-tree-ref",
1358
+ title: "引用到输入框(失败则复制 @path)",
1359
+ onClick: (e) => {
1360
+ e.stopPropagation();
1361
+ copyRef(entry.path);
1362
+ }
1363
+ }, "@引用");
1364
+ const renderNode = (entry, depth) => {
1365
+ const pad = { paddingLeft: 6 + depth * 20 };
1366
+ const isSelected = selectedPath === entry.path;
1367
+ const rowClass = "artifacts-tree-row" + (entry.hidden ? " artifacts-tree-hidden" : "") + (isSelected ? " is-selected" : "");
1368
+ if (entry.isDir) {
1369
+ const isExpanded = !!expanded[entry.path];
1370
+ const node = children[entry.path];
1371
+ return /* @__PURE__ */ h("div", { key: entry.path }, /* @__PURE__ */ h("div", {
1372
+ role: "button",
1373
+ tabIndex: 0,
1374
+ className: rowClass + " artifacts-tree-dir",
1375
+ style: pad,
1376
+ onClick: () => toggle(entry.path),
1377
+ onKeyDown: (ev) => {
1378
+ if (ev.key === "Enter" || ev.key === " ") {
1379
+ ev.preventDefault();
1380
+ toggle(entry.path);
1381
+ }
1382
+ },
1383
+ title: entry.path
1384
+ }, isExpanded ? /* @__PURE__ */ h(FolderOpenIcon, { size: 14 }) : /* @__PURE__ */ h(FolderClosedIcon, { size: 14 }), /* @__PURE__ */ h("span", { className: "artifacts-tree-name" }, entry.name), rowActions(entry)), isExpanded ? node && node.loading ? /* @__PURE__ */ h("div", {
1385
+ className: "artifacts-tree-row artifacts-tree-loading",
1386
+ style: { paddingLeft: 6 + (depth + 1) * 20 + 20 }
1387
+ }, "加载中…") : node && node.error ? /* @__PURE__ */ h("div", {
1388
+ className: "artifacts-tree-row artifacts-tree-error",
1389
+ style: { paddingLeft: 6 + (depth + 1) * 20 + 20 }
1390
+ }, node.error) : node && node.entries ? node.entries.map((c) => renderNode(c, depth + 1)) : null : null);
1391
+ }
1392
+ return /* @__PURE__ */ h("div", {
1393
+ role: "button",
1394
+ tabIndex: 0,
1395
+ className: rowClass,
1396
+ style: pad,
1397
+ onClick: () => {
1398
+ if (onOpen) onOpen(entry.path);
1399
+ },
1400
+ onKeyDown: (ev) => {
1401
+ if (ev.key === "Enter" || ev.key === " ") {
1402
+ ev.preventDefault();
1403
+ if (onOpen) onOpen(entry.path);
1404
+ }
1405
+ },
1406
+ title: entry.path
1407
+ }, /* @__PURE__ */ h(FileCodeIcon, { size: 14 }), /* @__PURE__ */ h("span", { className: "artifacts-tree-name" }, entry.name), rowActions(entry));
1408
+ };
1409
+ return /* @__PURE__ */ h("div", { className: "artifacts-tree" }, /* @__PURE__ */ h("div", { className: "artifacts-tree-body" }, !root ? /* @__PURE__ */ h("div", { className: "artifacts-hint" }, "加载文件树…") : !root.entries || !root.entries.length ? /* @__PURE__ */ h("div", { className: "artifacts-hint" }, "(空目录)") : root.entries.map((e) => renderNode(e, 0))));
1410
+ }
1411
+ function ArtifactsPanel() {
1412
+ const open = useOpen();
1413
+ const settings = useSettings();
1414
+ const [tabs, setTabs] = React.useState([]);
1415
+ const [activeKey, setActiveKey] = React.useState(null);
1416
+ const [previewHidden, setPreviewHidden] = React.useState(false);
1417
+ const sessionId = useSessionId();
1418
+ const firstSession = React.useRef(true);
1419
+ React.useEffect(() => {
1420
+ if (firstSession.current) {
1421
+ firstSession.current = false;
1422
+ return;
1423
+ }
1424
+ setTabs([]);
1425
+ setActiveKey(null);
1426
+ setPreviewHidden(false);
1427
+ }, [sessionId]);
1428
+ const [notice, setNotice] = React.useState("");
1429
+ const [gitFiles, setGitFiles] = React.useState(null);
1430
+ const [gitError, setGitError] = React.useState(null);
1431
+ const [panelWidth, setPanelWidth] = React.useState(null);
1432
+ const [resizing, setResizing] = React.useState(false);
1433
+ const [activeView, setActiveView] = React.useState(() => settings.showFileTree ? "tree" : "git");
1434
+ const [treeRefresh, setTreeRefresh] = React.useState(0);
1435
+ const [gitRefresh, setGitRefresh] = React.useState(0);
1436
+ const noticeTimer = React.useRef(null);
1437
+ React.useEffect(() => {
1438
+ if (!open || activeView !== "git") return;
1439
+ let alive = true;
1440
+ const load = () => {
1441
+ host.gitStatus(currentSessionId()).then((res) => {
1442
+ if (!alive) return;
1443
+ if (res && res.ok) {
1444
+ setGitFiles(Array.isArray(res.entries) ? res.entries : []);
1445
+ setGitError(null);
1446
+ } else {
1447
+ setGitFiles([]);
1448
+ setGitError(res && res.error || "git status 失败");
1449
+ }
1450
+ }).catch((e) => {
1451
+ if (alive) setGitError(e instanceof Error && e.message ? String(e.message) : String(e));
1452
+ });
1453
+ };
1454
+ load();
1455
+ let dispose;
1456
+ if (settings.autoRefresh) dispose = ctx.interval(load, 2e3);
1457
+ return () => {
1458
+ alive = false;
1459
+ if (dispose) dispose();
1460
+ };
1461
+ }, [
1462
+ open,
1463
+ activeView,
1464
+ settings.autoRefresh,
1465
+ gitRefresh,
1466
+ sessionId
1467
+ ]);
1468
+ React.useEffect(() => {
1469
+ const KEY = "dsh-flyout-sidebar:session";
1470
+ const write = () => {
1471
+ try {
1472
+ const sid = currentSessionId();
1473
+ if (localStorage.getItem(KEY) !== sid) localStorage.setItem(KEY, sid || "");
1474
+ } catch {}
1475
+ };
1476
+ write();
1477
+ let list;
1478
+ try {
1479
+ list = ctx.get("sessions")?.list;
1480
+ } catch {
1481
+ list = void 0;
1482
+ }
1483
+ if (!list || typeof list.subscribe !== "function") return;
1484
+ return list.subscribe(write);
1485
+ }, []);
1486
+ React.useEffect(() => {
1487
+ const KEY = "dsh-flyout-sidebar:theme";
1488
+ const isDark = () => {
1489
+ if (document.documentElement.hasAttribute("data-ds-dark-theme")) return true;
1490
+ if (document.body && document.body.hasAttribute("data-ds-dark-theme")) return true;
1491
+ return false;
1492
+ };
1493
+ const write = () => {
1494
+ try {
1495
+ const v = isDark() ? "dark" : "light";
1496
+ if (localStorage.getItem(KEY) !== v) localStorage.setItem(KEY, v);
1497
+ } catch {}
1498
+ };
1499
+ write();
1500
+ if (typeof MutationObserver !== "function") return;
1501
+ const obs = new MutationObserver(write);
1502
+ const opts = {
1503
+ attributes: true,
1504
+ attributeFilter: ["data-ds-dark-theme"]
1505
+ };
1506
+ obs.observe(document.documentElement, opts);
1507
+ if (document.body) obs.observe(document.body, opts);
1508
+ return () => obs.disconnect();
1509
+ }, []);
1510
+ const minWidthPx = Math.max(80, Math.round(window.innerWidth * (settings.minPanelWidth || 0) / 100));
1511
+ const widthPx = panelWidth != null ? Math.max(panelWidth, minWidthPx) : minWidthPx;
1512
+ React.useEffect(() => {
1513
+ const root = document.documentElement;
1514
+ root.style.setProperty("--dsh-flyout-sidebar-width", open ? widthPx + "px" : "0px");
1515
+ return () => {
1516
+ root.style.setProperty("--dsh-flyout-sidebar-width", "0px");
1517
+ };
1518
+ }, [open, widthPx]);
1519
+ React.useEffect(() => {
1520
+ if (resizing) document.body.setAttribute("data-dsh-flyout-dragging", "");
1521
+ else document.body.removeAttribute("data-dsh-flyout-dragging");
1522
+ return () => {
1523
+ document.body.removeAttribute("data-dsh-flyout-dragging");
1524
+ };
1525
+ }, [resizing]);
1526
+ if (!open) return null;
1527
+ const flyoutHref = "/flyout-sidebar" + (sessionId ? "?sessionId=" + encodeURIComponent(sessionId) : "");
1528
+ const startResize = (e) => {
1529
+ e.preventDefault();
1530
+ setResizing(true);
1531
+ const rightOffset = (() => {
1532
+ const v = document.documentElement.style.getPropertyValue("--dsh-sidebar-width");
1533
+ const n = parseFloat(v);
1534
+ return Number.isFinite(n) ? n : 0;
1535
+ })();
1536
+ const onMove = (ev) => {
1537
+ const w = window.innerWidth - ev.clientX - rightOffset;
1538
+ setPanelWidth(Math.max(minWidthPx, Math.min(w, window.innerWidth - rightOffset - 24)));
1539
+ };
1540
+ const onUp = () => {
1541
+ setResizing(false);
1542
+ document.removeEventListener("mousemove", onMove);
1543
+ document.removeEventListener("mouseup", onUp);
1544
+ };
1545
+ document.addEventListener("mousemove", onMove);
1546
+ document.addEventListener("mouseup", onUp);
1547
+ };
1548
+ const flash = (msg) => {
1549
+ setNotice(msg);
1550
+ if (noticeTimer.current) clearTimeout(noticeTimer.current);
1551
+ noticeTimer.current = setTimeout(() => setNotice(""), 1600);
1552
+ };
1553
+ const copyText = (text, msg) => {
1554
+ const done = () => flash(msg);
1555
+ if (navigator.clipboard && navigator.clipboard.writeText) navigator.clipboard.writeText(text).then(done, () => {
1556
+ fallbackCopy(text);
1557
+ done();
1558
+ });
1559
+ else {
1560
+ fallbackCopy(text);
1561
+ done();
1562
+ }
1563
+ };
1564
+ const quotePath = (path) => {
1565
+ if (quoteToComposer(path)) {
1566
+ flash("已插入输入框");
1567
+ return;
1568
+ }
1569
+ copyText("@" + path, "已复制 @引用(未能写入输入框)");
1570
+ };
1571
+ const patchTab = (key, patch) => setTabs((prev) => prev.map((t) => t.key === key ? {
1572
+ ...t,
1573
+ ...patch
1574
+ } : t));
1575
+ const openTab = (key, path, git, initial) => {
1576
+ setPreviewHidden(false);
1577
+ setTabs((prev) => {
1578
+ const i = prev.findIndex((t) => t.key === key);
1579
+ if (i >= 0) {
1580
+ const next = prev.slice();
1581
+ const cur = next[i];
1582
+ if (cur) next[i] = {
1583
+ ...cur,
1584
+ ...initial
1585
+ };
1586
+ return next;
1587
+ }
1588
+ return prev.concat([{
1589
+ key,
1590
+ path,
1591
+ git,
1592
+ ...initial
1593
+ }]);
1594
+ });
1595
+ setActiveKey(key);
1596
+ };
1597
+ const closeTab = (key) => {
1598
+ const idx = tabs.findIndex((t) => t.key === key);
1599
+ const next = tabs.filter((t) => t.key !== key);
1600
+ setTabs(next);
1601
+ if (activeKey === key) setActiveKey(next.length ? next[Math.min(idx, next.length - 1)]?.key ?? null : null);
1602
+ };
1603
+ const activeTab = tabs.find((t) => t.key === activeKey) || null;
1604
+ const openFile = (path) => {
1605
+ const key = "p:" + path;
1606
+ const type = extType(path);
1607
+ const initial = {
1608
+ loading: false,
1609
+ type,
1610
+ diff: null
1611
+ };
1612
+ if (type !== "image" && type !== "pdf") initial.loading = true;
1613
+ openTab(key, path, false, initial);
1614
+ if (type === "image" || type === "pdf") return;
1615
+ host.readArtifact(path).then((res) => {
1616
+ patchTab(key, {
1617
+ loading: false,
1618
+ ...res
1619
+ });
1620
+ }).catch((e) => {
1621
+ patchTab(key, {
1622
+ loading: false,
1623
+ ok: false,
1624
+ error: String(e instanceof Error && e.message ? e.message : e)
1625
+ });
1626
+ });
1627
+ };
1628
+ const openGitDiff = (path) => {
1629
+ const key = "g:" + path;
1630
+ openTab(key, path, true, { loading: true });
1631
+ host.gitDiff(path, currentSessionId()).then((res) => {
1632
+ patchTab(key, {
1633
+ loading: false,
1634
+ ...res
1635
+ });
1636
+ }).catch((e) => {
1637
+ patchTab(key, {
1638
+ loading: false,
1639
+ ok: false,
1640
+ error: String(e instanceof Error && e.message ? e.message : e)
1641
+ });
1642
+ });
1643
+ };
1644
+ const gitLabel = (e) => {
1645
+ if (e.x === "?" || e.y === "?") return "U";
1646
+ return (e.y !== " " ? e.y : e.x) || "M";
1647
+ };
1648
+ const gitTitle = (e) => {
1649
+ const label = gitLabel(e);
1650
+ const map = {
1651
+ U: "未跟踪",
1652
+ A: "新增",
1653
+ M: "修改",
1654
+ D: "删除",
1655
+ R: "重命名",
1656
+ C: "复制"
1657
+ };
1658
+ const staged = e.x !== " " && e.x !== "?";
1659
+ return (map[label] || label) + (staged ? "(已暂存)" : "(未暂存)");
1660
+ };
1661
+ const gitListRows = [];
1662
+ if (gitError) gitListRows.push(/* @__PURE__ */ h("div", {
1663
+ key: "err",
1664
+ className: "artifacts-tree-error artifacts-git-error"
1665
+ }, gitError));
1666
+ else if (gitFiles == null) gitListRows.push(/* @__PURE__ */ h("div", {
1667
+ key: "load",
1668
+ className: "artifacts-empty"
1669
+ }, "加载变更列表…"));
1670
+ else if (!gitFiles.length) gitListRows.push(/* @__PURE__ */ h("div", {
1671
+ key: "empty",
1672
+ className: "artifacts-empty"
1673
+ }, "没有未提交的变更"));
1674
+ (gitFiles || []).forEach((e) => {
1675
+ const label = gitLabel(e);
1676
+ const isActive = !!(activeTab && activeTab.git && activeTab.path === e.path);
1677
+ gitListRows.push(/* @__PURE__ */ h("div", {
1678
+ key: e.path,
1679
+ className: "artifacts-item" + (isActive ? " is-active" : "")
1680
+ }, /* @__PURE__ */ h("button", {
1681
+ type: "button",
1682
+ className: "artifacts-item-main",
1683
+ title: gitTitle(e),
1684
+ onClick: () => openGitDiff(e.path)
1685
+ }, /* @__PURE__ */ h("div", { className: "artifacts-item-row" }, /* @__PURE__ */ h("span", { className: "artifacts-git-badge artifacts-git-badge-" + label }, label), /* @__PURE__ */ h("span", { className: "artifacts-item-base" }, basename(e.path)), e.origPath ? /* @__PURE__ */ h("span", { className: "artifacts-git-orig" }, "← ", basename(e.origPath)) : null), /* @__PURE__ */ h("div", { className: "artifacts-item-full" }, e.path)), /* @__PURE__ */ h("div", { className: "artifacts-item-actions" }, /* @__PURE__ */ h("button", {
1686
+ type: "button",
1687
+ className: "artifacts-minibtn",
1688
+ title: "复制路径",
1689
+ onClick: () => copyText(e.path, "已复制路径")
1690
+ }, "⧉"), /* @__PURE__ */ h("button", {
1691
+ type: "button",
1692
+ className: "artifacts-minibtn",
1693
+ title: "@引用到输入框",
1694
+ onClick: () => quotePath(e.path)
1695
+ }, "@"))));
1696
+ });
1697
+ const previewOverlay = tabs.length && !previewHidden ? /* @__PURE__ */ h("div", {
1698
+ className: "artifacts-preview-overlay",
1699
+ role: "region",
1700
+ "aria-label": "文件预览"
1701
+ }, /* @__PURE__ */ h("div", { className: "artifacts-preview-overlay-tabs" }, /* @__PURE__ */ h("div", { className: "artifacts-ptabs-scroll" }, tabs.map((t) => /* @__PURE__ */ h("div", {
1702
+ key: t.key,
1703
+ className: "artifacts-ptab" + (t.key === activeKey ? " is-active" : ""),
1704
+ title: (t.git ? "[diff] " : "") + (t.path || ""),
1705
+ onClick: () => setActiveKey(t.key)
1706
+ }, /* @__PURE__ */ h("span", { className: "artifacts-ptab-name" }, basename(t.path || "")), /* @__PURE__ */ h("button", {
1707
+ type: "button",
1708
+ className: "artifacts-ptab-close",
1709
+ title: "关闭标签页",
1710
+ onClick: (e) => {
1711
+ e.stopPropagation();
1712
+ closeTab(t.key);
1713
+ }
1714
+ }, "×")))), /* @__PURE__ */ h("button", {
1715
+ type: "button",
1716
+ className: "artifacts-preview-hide",
1717
+ title: "隐藏预览(标签页保留)",
1718
+ onClick: () => setPreviewHidden(true)
1719
+ }, /* @__PURE__ */ h(PanelCollapseIcon, { size: 16 }))), activeTab ? renderPreview(activeTab) : null) : null;
1720
+ return /* @__PURE__ */ h(Fragment, null, previewOverlay, /* @__PURE__ */ h("div", {
1721
+ className: "artifacts-panel" + (resizing ? " artifacts-resizing" : ""),
1722
+ style: { width: widthPx },
1723
+ role: "dialog",
1724
+ "aria-label": "Artifacts"
1725
+ }, /* @__PURE__ */ h("div", {
1726
+ className: "artifacts-resize",
1727
+ title: "拖动调整宽度",
1728
+ onMouseDown: startResize
1729
+ }), /* @__PURE__ */ h("div", { className: "artifacts-head" }, /* @__PURE__ */ h("div", { className: "artifacts-head-left" }, /* @__PURE__ */ h("button", {
1730
+ type: "button",
1731
+ className: "artifacts-toggle",
1732
+ title: "收起侧边栏",
1733
+ onClick: () => store.setOpen(false)
1734
+ }, /* @__PURE__ */ h(PanelIcon, { size: 16 })), /* @__PURE__ */ h("a", {
1735
+ className: "artifacts-link",
1736
+ href: flyoutHref,
1737
+ target: "_blank",
1738
+ rel: "noreferrer noopener",
1739
+ title: "弹出式侧边栏 — 在新标签页打开(可拖到另一块显示器)"
1740
+ }, /* @__PURE__ */ h(FlyoutIcon, { size: 16 }))), /* @__PURE__ */ h("span", { className: "artifacts-spacer" }), notice ? /* @__PURE__ */ h("span", { className: "artifacts-notice" }, notice) : null, /* @__PURE__ */ h("button", {
1741
+ type: "button",
1742
+ className: "artifacts-toggle",
1743
+ title: activeView === "tree" ? "刷新文件树" : "刷新变更列表",
1744
+ onClick: () => {
1745
+ if (activeView === "tree") setTreeRefresh((n) => n + 1);
1746
+ else setGitRefresh((n) => n + 1);
1747
+ }
1748
+ }, /* @__PURE__ */ h(RefreshIcon, { size: 16 })), settings.showFileTree ? /* @__PURE__ */ h("button", {
1749
+ type: "button",
1750
+ className: "artifacts-iconbtn artifacts-viewbtn" + (activeView === "git" ? " is-active" : ""),
1751
+ title: activeView === "tree" ? "查看 Git 变更(未提交)" : "返回文件列表",
1752
+ "aria-pressed": activeView === "git",
1753
+ onClick: () => setActiveView(activeView === "tree" ? "git" : "tree")
1754
+ }, activeView === "tree" ? /* @__PURE__ */ h(GitBranchIcon, { size: 16 }) : /* @__PURE__ */ h(FolderClosedIcon, { size: 16 })) : null), /* @__PURE__ */ h("div", { className: "artifacts-main" }, /* @__PURE__ */ h("div", {
1755
+ className: "artifacts-body",
1756
+ style: { flex: "1 1 auto" }
1757
+ }, activeView === "tree" && settings.showFileTree ? /* @__PURE__ */ h(FileTree, {
1758
+ onOpen: openFile,
1759
+ selectedPath: activeTab && !activeTab.git ? activeTab.path : null,
1760
+ refreshToken: treeRefresh
1761
+ }) : gitListRows))));
1762
+ }
1763
+ function CornerButton() {
1764
+ const open = useOpen();
1765
+ if (open) return null;
1766
+ return /* @__PURE__ */ h("button", {
1767
+ type: "button",
1768
+ className: "artifacts-corner-btn",
1769
+ title: "弹出式侧边栏",
1770
+ "aria-expanded": open,
1771
+ onClick: () => store.toggle()
1772
+ }, /* @__PURE__ */ h(PanelIcon, { size: 18 }));
1773
+ }
1774
+ function SettingsToggle({ label, desc, value, onToggle }) {
1775
+ return /* @__PURE__ */ h("div", { className: "artifacts-setrow" }, /* @__PURE__ */ h("div", { className: "artifacts-settext" }, /* @__PURE__ */ h("div", { className: "artifacts-settitle" }, label), /* @__PURE__ */ h("div", { className: "artifacts-setdesc" }, desc)), /* @__PURE__ */ h("label", { className: "artifacts-switch" }, /* @__PURE__ */ h("input", {
1776
+ type: "checkbox",
1777
+ checked: value,
1778
+ "aria-label": label,
1779
+ onChange: (e) => onToggle(e.currentTarget.checked)
1780
+ }), /* @__PURE__ */ h("span", {
1781
+ className: "artifacts-switch-track",
1782
+ "aria-hidden": "true"
1783
+ }, /* @__PURE__ */ h("span", { className: "artifacts-switch-thumb" }))));
1784
+ }
1785
+ function SettingsSection() {
1786
+ const settings = useSettings();
1787
+ const set = (key, value) => settingsStore.set(key, value);
1788
+ return /* @__PURE__ */ h("div", { className: "artifacts-settings" }, /* @__PURE__ */ h("p", { className: "artifacts-setintro" }, "管理「Flyout Sidebar」的显示与行为。"), /* @__PURE__ */ h("div", { className: "artifacts-setgroup" }, /* @__PURE__ */ h(SettingsToggle, {
1789
+ label: "默认展开",
1790
+ desc: "页面加载后侧边栏默认展开;关闭则默认收起,点右上角图标再打开。",
1791
+ value: settings.defaultOpen,
1792
+ onToggle: (v) => set("defaultOpen", v)
1793
+ }), /* @__PURE__ */ h(SettingsToggle, {
1794
+ label: "自动刷新",
1795
+ desc: "开启后侧边栏展开时将即时同步并更新产物列表",
1796
+ value: settings.autoRefresh,
1797
+ onToggle: (v) => set("autoRefresh", v)
1798
+ }), /* @__PURE__ */ h(SettingsToggle, {
1799
+ label: "文件树",
1800
+ desc: "在侧边栏显示「文件树」标签页,浏览工作区目录。",
1801
+ value: settings.showFileTree,
1802
+ onToggle: (v) => set("showFileTree", v)
1803
+ }), /* @__PURE__ */ h("div", { className: "artifacts-setrow" }, /* @__PURE__ */ h("div", { className: "artifacts-settext" }, /* @__PURE__ */ h("div", { className: "artifacts-settitle" }, "最短面板宽度"), /* @__PURE__ */ h("div", { className: "artifacts-setdesc" }, "面板的最小宽度(占窗口宽度的百分比,20–60);更宽可通过拖动面板左边缘调整。")), /* @__PURE__ */ h("div", { className: "artifacts-setcontrol" }, /* @__PURE__ */ h("input", {
1804
+ type: "number",
1805
+ className: "artifacts-widthinput",
1806
+ min: 20,
1807
+ max: 60,
1808
+ value: settings.minPanelWidth,
1809
+ onChange: (e) => {
1810
+ const n = parseInt(e.currentTarget.value, 10);
1811
+ if (Number.isNaN(n)) return;
1812
+ set("minPanelWidth", Math.max(20, Math.min(60, n)));
1813
+ }
1814
+ }), /* @__PURE__ */ h("span", { className: "artifacts-suffix" }, "%")))));
1815
+ }
1816
+ //#endregion
1817
+ //#region src/client/index.tsx
1818
+ window.__ModuleLoader__.load({
1819
+ id: "dsh-flyout-sidebar",
1820
+ factory: (require) => {
1821
+ initReact(require("react"));
1822
+ return {
1823
+ inject: ["timer"],
1824
+ apply(ctx) {
1825
+ initClient(ctx);
1826
+ const slots = ctx.get("slots");
1827
+ if (slots === void 0) return;
1828
+ insertStyles();
1829
+ slots.inject("shell.overlay", () => slots.register({
1830
+ name: "shell.overlay",
1831
+ id: "artifacts-sidebar-trigger",
1832
+ order: 40,
1833
+ label: "Artifacts"
1834
+ }, CornerButton));
1835
+ slots.inject("shell.overlay", () => slots.register({
1836
+ name: "shell.overlay",
1837
+ id: "artifacts-sidebar-panel",
1838
+ order: 50,
1839
+ label: "Artifacts Panel"
1840
+ }, () => /* @__PURE__ */ h(ArtifactsPanel, null)));
1841
+ slots.inject("settings.section", () => slots.register({
1842
+ name: "settings.section",
1843
+ id: "artifacts-sidebar",
1844
+ order: 90,
1845
+ label: "Flyout Sidebar"
1846
+ }, SettingsSection));
1847
+ }
1848
+ };
1849
+ }
1850
+ });
1851
+ //#endregion
1852
+ })();