@jingyi0605/codingns4dsh 0.1.2 → 0.1.7-rc.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.
Files changed (171) hide show
  1. package/README.en.md +4 -4
  2. package/README.md +14 -5
  3. package/data/build/dist/client/account-bar.js +57 -3
  4. package/data/build/dist/client/bundle.js +36246 -268
  5. package/data/build/dist/client/cli-slots.js +60 -18
  6. package/data/build/dist/client/dsh-h5-bootstrap.d.ts +2 -0
  7. package/data/build/dist/client/dsh-h5-bootstrap.js +79 -13
  8. package/data/build/dist/client/features/file-management-panel.d.ts +5 -0
  9. package/data/build/dist/client/features/file-management-panel.js +48 -0
  10. package/data/build/dist/client/features/file-management.d.ts +4 -0
  11. package/data/build/dist/client/features/file-management.js +50 -0
  12. package/data/build/dist/client/features/index.d.ts +10 -1
  13. package/data/build/dist/client/features/index.js +12 -1
  14. package/data/build/dist/client/features/login-protection-panel.js +1 -1
  15. package/data/build/dist/client/features/login-protection-session.d.ts +2 -0
  16. package/data/build/dist/client/features/login-protection-session.js +18 -0
  17. package/data/build/dist/client/features/peer-host.d.ts +4 -0
  18. package/data/build/dist/client/features/peer-host.js +89 -0
  19. package/data/build/dist/client/features/reverse-proxy-panel.js +23 -7
  20. package/data/build/dist/client/features/reverse-proxy.d.ts +1 -2
  21. package/data/build/dist/client/features/reverse-proxy.js +75 -5
  22. package/data/build/dist/client/features/types.d.ts +9 -0
  23. package/data/build/dist/client/features/workspace-session-enhancement-panel.js +10 -0
  24. package/data/build/dist/client/features/workspace-session-enhancement.js +18 -1
  25. package/data/build/dist/client/file-management-dom.d.ts +12 -0
  26. package/data/build/dist/client/file-management-dom.js +527 -0
  27. package/data/build/dist/client/git-management.js +24 -5
  28. package/data/build/dist/client/host-navigation.d.ts +29 -0
  29. package/data/build/dist/client/host-navigation.js +29 -0
  30. package/data/build/dist/client/host-router.d.ts +3 -0
  31. package/data/build/dist/client/host-router.js +2 -0
  32. package/data/build/dist/client/index.d.ts +6 -1
  33. package/data/build/dist/client/index.js +30 -10
  34. package/data/build/dist/client/locale.js +36 -7
  35. package/data/build/dist/client/peer-host-connection-button.d.ts +13 -0
  36. package/data/build/dist/client/peer-host-connection-button.js +69 -0
  37. package/data/build/dist/client/peer-host-management-api.d.ts +40 -0
  38. package/data/build/dist/client/peer-host-management-api.js +33 -0
  39. package/data/build/dist/client/peer-host-management-panel.d.ts +13 -0
  40. package/data/build/dist/client/peer-host-management-panel.js +181 -0
  41. package/data/build/dist/client/peer-host-native-session-ui.d.ts +60 -0
  42. package/data/build/dist/client/peer-host-native-session-ui.js +372 -0
  43. package/data/build/dist/client/peer-host-scoped-client.d.ts +67 -0
  44. package/data/build/dist/client/peer-host-scoped-client.js +316 -0
  45. package/data/build/dist/client/peer-host-session-controller.d.ts +30 -0
  46. package/data/build/dist/client/peer-host-session-controller.js +95 -0
  47. package/data/build/dist/client/quick-phrase-slot.js +14 -0
  48. package/data/build/dist/client/remote-web-context.js +26 -4
  49. package/data/build/dist/client/session-changed-files-view.d.ts +15 -0
  50. package/data/build/dist/client/session-changed-files-view.js +337 -0
  51. package/data/build/dist/client/settings-bridge.d.ts +14 -4
  52. package/data/build/dist/client/settings-bridge.js +59 -12
  53. package/data/build/dist/client/settings-section.js +17 -4
  54. package/data/build/dist/client/subscription-slot.js +21 -8
  55. package/data/build/dist/client/workspace-session-rightbar-dom.d.ts +28 -0
  56. package/data/build/dist/client/workspace-session-rightbar-dom.js +291 -0
  57. package/data/build/dist/dsh-capabilities/client/config-forms-adapter.js +25 -33
  58. package/data/build/dist/dsh-capabilities/client/settings-scope-adapter.d.ts +7 -1
  59. package/data/build/dist/dsh-capabilities/client/settings-scope-adapter.js +34 -11
  60. package/data/build/dist/dsh-capabilities/matrix.js +9 -0
  61. package/data/build/dist/dsh-capabilities/routes.js +35 -0
  62. package/data/build/dist/dsh-capabilities/settings-store.d.ts +12 -0
  63. package/data/build/dist/dsh-capabilities/settings-store.js +34 -0
  64. package/data/build/dist/dsh-capabilities/types.d.ts +1 -1
  65. package/data/build/dist/features/host-router.d.ts +26 -0
  66. package/data/build/dist/features/host-router.js +110 -0
  67. package/data/build/dist/features/registry.js +3 -0
  68. package/data/build/dist/host/cli-adapters/codex-driver.d.ts +13 -1
  69. package/data/build/dist/host/cli-adapters/codex-driver.js +528 -72
  70. package/data/build/dist/host/cli-adapters/command-code-driver.d.ts +21 -2
  71. package/data/build/dist/host/cli-adapters/command-code-driver.js +408 -105
  72. package/data/build/dist/host/cli-adapters/driver.d.ts +2 -0
  73. package/data/build/dist/host/cli-adapters/dsh-message-projector.d.ts +2 -0
  74. package/data/build/dist/host/cli-adapters/dsh-message-projector.js +47 -28
  75. package/data/build/dist/host/cli-adapters/dsh-tool-history.js +37 -0
  76. package/data/build/dist/host/cli-adapters/feature.js +94 -10
  77. package/data/build/dist/host/cli-adapters/json-rpc-process.d.ts +6 -0
  78. package/data/build/dist/host/cli-adapters/json-rpc-process.js +14 -3
  79. package/data/build/dist/host/cli-adapters/kimi-driver.js +3 -3
  80. package/data/build/dist/host/cli-adapters/model-catalog.d.ts +6 -0
  81. package/data/build/dist/host/cli-adapters/model-catalog.js +8 -0
  82. package/data/build/dist/host/cli-adapters/opencode-driver.js +14 -2
  83. package/data/build/dist/host/cli-adapters/pi-driver.js +2 -2
  84. package/data/build/dist/host/cli-adapters/process-utils.d.ts +8 -0
  85. package/data/build/dist/host/cli-adapters/process-utils.js +48 -0
  86. package/data/build/dist/host/cli-adapters/registry.d.ts +6 -1
  87. package/data/build/dist/host/cli-adapters/registry.js +41 -7
  88. package/data/build/dist/host/cli-adapters/rpc-driver-utils.js +23 -4
  89. package/data/build/dist/host/cli-adapters/standard-stream-driver.d.ts +3 -0
  90. package/data/build/dist/host/cli-adapters/standard-stream-driver.js +36 -15
  91. package/data/build/dist/host/dsh-device-runtime.d.ts +2 -0
  92. package/data/build/dist/host/dsh-device-runtime.js +55 -29
  93. package/data/build/dist/host/dsh-runtime-version.d.ts +3 -2
  94. package/data/build/dist/host/dsh-runtime-version.js +71 -2
  95. package/data/build/dist/host/features/auth.js +10 -2
  96. package/data/build/dist/host/features/file-management.d.ts +5 -0
  97. package/data/build/dist/host/features/file-management.js +267 -0
  98. package/data/build/dist/host/features/git-management.js +24 -2
  99. package/data/build/dist/host/features/index.d.ts +2 -0
  100. package/data/build/dist/host/features/index.js +6 -0
  101. package/data/build/dist/host/features/peer-host.d.ts +22 -0
  102. package/data/build/dist/host/features/peer-host.js +322 -0
  103. package/data/build/dist/host/features/types.d.ts +4 -0
  104. package/data/build/dist/host/host-router.d.ts +3 -0
  105. package/data/build/dist/host/host-router.js +2 -0
  106. package/data/build/dist/host/index-injection.d.ts +17 -0
  107. package/data/build/dist/host/index-injection.js +24 -0
  108. package/data/build/dist/host/index.d.ts +4 -1
  109. package/data/build/dist/host/index.js +41 -7
  110. package/data/build/dist/host/lan-access-dsh.d.ts +34 -1
  111. package/data/build/dist/host/lan-access-dsh.js +145 -26
  112. package/data/build/dist/host/modules/peer-host/host-api-proxy-service.d.ts +52 -0
  113. package/data/build/dist/host/modules/peer-host/host-api-proxy-service.js +166 -0
  114. package/data/build/dist/host/modules/peer-host/host-ws-connector.d.ts +31 -0
  115. package/data/build/dist/host/modules/peer-host/host-ws-connector.js +108 -0
  116. package/data/build/dist/host/modules/peer-host/host-ws-proxy-service.d.ts +26 -0
  117. package/data/build/dist/host/modules/peer-host/host-ws-proxy-service.js +159 -0
  118. package/data/build/dist/host/modules/peer-host/peer-host-aggregate-service.d.ts +50 -0
  119. package/data/build/dist/host/modules/peer-host/peer-host-aggregate-service.js +97 -0
  120. package/data/build/dist/host/modules/peer-host/peer-host-diagnostics.d.ts +19 -0
  121. package/data/build/dist/host/modules/peer-host/peer-host-diagnostics.js +54 -0
  122. package/data/build/dist/host/modules/peer-host/peer-host-handshake.d.ts +44 -0
  123. package/data/build/dist/host/modules/peer-host/peer-host-handshake.js +133 -0
  124. package/data/build/dist/host/modules/peer-host/peer-host-relay.d.ts +65 -0
  125. package/data/build/dist/host/modules/peer-host/peer-host-relay.js +196 -0
  126. package/data/build/dist/host/modules/peer-host/peer-host-session.d.ts +42 -0
  127. package/data/build/dist/host/modules/peer-host/peer-host-session.js +152 -0
  128. package/data/build/dist/host/modules/peer-host/peer-host-store.d.ts +92 -0
  129. package/data/build/dist/host/modules/peer-host/peer-host-store.js +323 -0
  130. package/data/build/dist/host/modules/peer-host/peer-host-ws-gateway.d.ts +34 -0
  131. package/data/build/dist/host/modules/peer-host/peer-host-ws-gateway.js +156 -0
  132. package/data/build/dist/host/native-session-bridge.d.ts +18 -2
  133. package/data/build/dist/host/native-session-bridge.js +232 -8
  134. package/data/build/dist/host/remote-web-runtime.js +26 -1
  135. package/data/build/dist/host/rpc.js +8 -3
  136. package/data/build/dist/host/session-changes.d.ts +5 -0
  137. package/data/build/dist/host/session-changes.js +163 -0
  138. package/data/build/dist/host/settings.js +6 -0
  139. package/data/build/dist/index.d.ts +1 -1
  140. package/data/build/dist/index.js +1 -1
  141. package/data/build/dist/shared/contracts/cli-adapter.d.ts +23 -2
  142. package/data/build/dist/shared/contracts/config.d.ts +15 -0
  143. package/data/build/dist/shared/contracts/config.js +10 -0
  144. package/data/build/dist/shared/contracts/dsh-device.d.ts +2 -0
  145. package/data/build/dist/shared/contracts/errors.d.ts +14 -0
  146. package/data/build/dist/shared/contracts/errors.js +15 -0
  147. package/data/build/dist/shared/contracts/feature.d.ts +2 -0
  148. package/data/build/dist/shared/contracts/file-management.d.ts +6 -0
  149. package/data/build/dist/shared/contracts/file-management.js +3 -0
  150. package/data/build/dist/shared/contracts/peer-host.d.ts +116 -15
  151. package/data/build/dist/shared/contracts/peer-host.js +19 -0
  152. package/data/build/dist/shared/contracts/version.d.ts +1 -1
  153. package/data/build/dist/shared/contracts/version.js +1 -1
  154. package/data/build/dist/shared/index.d.ts +4 -3
  155. package/data/build/dist/shared/index.js +2 -2
  156. package/data/build/dist/transport/debug.d.ts +5 -0
  157. package/data/build/dist/transport/debug.js +23 -1
  158. package/data/build/dist/transport/dsh-transport.d.ts +2 -0
  159. package/data/build/dist/transport/dsh-transport.js +11 -0
  160. package/data/build/dist/transport/webrtc-client.d.ts +2 -0
  161. package/data/build/dist/transport/webrtc-client.js +2 -1
  162. package/data/build/dist/transport/webrtc-host.d.ts +2 -0
  163. package/data/build/dist/transport/webrtc-host.js +6 -3
  164. package/data/build/dist/typert.host.d.ts +1 -1
  165. package/data/build/dist/typert.host.js +2 -1
  166. package/dsh.bundle.patch +10 -0
  167. package/package.json +19 -11
  168. package/profile/README.md +2 -2
  169. package/profile/package.json +2 -2
  170. package/profile/version.json +1 -1
  171. package/version.json +1 -1
@@ -0,0 +1,527 @@
1
+ import { callCodingNsRpc } from './settings-bridge.js';
2
+ import { basicSetup, EditorView } from 'codemirror';
3
+ import { css } from '@codemirror/lang-css';
4
+ import { html } from '@codemirror/lang-html';
5
+ import { javascript } from '@codemirror/lang-javascript';
6
+ import { json } from '@codemirror/lang-json';
7
+ import { markdown } from '@codemirror/lang-markdown';
8
+ import { python } from '@codemirror/lang-python';
9
+ import { sql } from '@codemirror/lang-sql';
10
+ const TEXT_EXTENSIONS = new Set(['.md', '.markdown', '.txt', '.ini', '.json', '.yaml', '.yml', '.toml', '.xml', '.csv', '.js', '.jsx', '.ts', '.tsx', '.css', '.html', '.htm', '.env', '.gitignore', '.conf', '.properties', '.sh', '.py', '.sql']);
11
+ /** 给 DSH 原生文件树和文本查看器补充文件操作,不接管 DSH 自己的渲染状态。 */
12
+ export function startFileManagementDom(rpc, initialOptions) {
13
+ // Client 功能也会在 H5/非浏览器测试环境被装配;没有 DOM 时保持惰性空实现。
14
+ if (typeof document === 'undefined')
15
+ return { setOptions: () => undefined, dispose: () => undefined };
16
+ let menu;
17
+ let clipboard;
18
+ let editor;
19
+ let options = { ...initialOptions };
20
+ let disposed = false;
21
+ const observer = typeof MutationObserver === 'undefined'
22
+ ? undefined
23
+ : new MutationObserver(() => { if (!disposed && options.fileEditor)
24
+ enhanceEditors(); });
25
+ const closeMenu = () => { menu?.remove(); menu = undefined; };
26
+ const onContextMenu = (event) => {
27
+ if (!options.menuEnhancement)
28
+ return;
29
+ const target = event.target?.closest('[data-files-entry][data-files-path]');
30
+ if (target == null)
31
+ return;
32
+ event.preventDefault();
33
+ event.stopPropagation();
34
+ openMenu(target, event.clientX, event.clientY);
35
+ };
36
+ const onDocumentClick = (event) => {
37
+ if (menu !== undefined && !menu.contains(event.target))
38
+ closeMenu();
39
+ };
40
+ const onKeyDown = (event) => { if (event.key === 'Escape')
41
+ closeMenu(); };
42
+ document.addEventListener('contextmenu', onContextMenu, true);
43
+ document.addEventListener('click', onDocumentClick, true);
44
+ document.addEventListener('keydown', onKeyDown, true);
45
+ observer?.observe(document.body, { childList: true, subtree: true });
46
+ if (options.fileEditor)
47
+ enhanceEditors();
48
+ const dispose = () => {
49
+ disposed = true;
50
+ closeMenu();
51
+ clearEditorEnhancements();
52
+ editor = undefined;
53
+ observer?.disconnect();
54
+ document.removeEventListener('contextmenu', onContextMenu, true);
55
+ document.removeEventListener('click', onDocumentClick, true);
56
+ document.removeEventListener('keydown', onKeyDown, true);
57
+ };
58
+ return {
59
+ setOptions(nextOptions) {
60
+ const previous = options;
61
+ options = { ...nextOptions };
62
+ if (!options.menuEnhancement)
63
+ closeMenu();
64
+ if (!options.fileEditor)
65
+ clearEditorEnhancements();
66
+ else if (!previous.fileEditor)
67
+ enhanceEditors();
68
+ },
69
+ dispose,
70
+ };
71
+ function openMenu(item, x, y) {
72
+ closeMenu();
73
+ const path = item.dataset.filesPath ?? '';
74
+ const kind = item.dataset.filesEntry === 'directory' ? 'directory' : 'file';
75
+ if (path === '')
76
+ return;
77
+ const base = kind === 'directory' ? path : parentPath(path);
78
+ const panel = item.closest('[data-files-state="tree"]');
79
+ const items = [
80
+ { label: kind === 'directory' ? '展开文件夹' : '打开文件', action: () => clickEntry(item) },
81
+ { label: '下载文件', disabled: kind !== 'file', action: () => void downloadFile({ path }) },
82
+ { label: '新建文件', action: () => void createEntry(base, false) },
83
+ { label: '新建目录', action: () => void createEntry(base, true) },
84
+ { label: '重命名/移动', action: () => void renameEntry(path) },
85
+ { label: '复制', action: () => { clipboard = { mode: 'copy', paths: [path] }; } },
86
+ { label: '剪切', action: () => { clipboard = { mode: 'cut', paths: [path] }; } },
87
+ { label: '粘贴', disabled: clipboard === undefined, action: () => void pasteEntry(base, panel) },
88
+ { label: '复制相对路径', action: () => void copyPath(item, false) },
89
+ { label: '复制绝对路径', action: () => void copyPath(item, true) },
90
+ { label: '添加到 Git 排除', action: () => void runMutation('git-ignore', { paths: [path] }, panel) },
91
+ { label: '删除', action: () => void deleteEntry(path, panel) },
92
+ ];
93
+ menu = document.createElement('div');
94
+ menu.setAttribute('role', 'menu');
95
+ menu.style.cssText = 'position:fixed;z-index:2147483647;min-width:190px;padding:5px;background:var(--dsw-alias-bg-layer-3,#242526);border:1px solid var(--dsw-alias-border-l2,#666);border-radius:8px;box-shadow:0 8px 30px #0008;color:var(--dsw-alias-label-primary,#eee);font:13px var(--dsw-font,system-ui,sans-serif)';
96
+ for (const item of items) {
97
+ const button = document.createElement('button');
98
+ button.type = 'button';
99
+ button.setAttribute('role', 'menuitem');
100
+ button.textContent = item.label;
101
+ button.disabled = item.disabled === true;
102
+ button.style.cssText = 'display:block;width:100%;padding:7px 10px;border:0;border-radius:4px;background:transparent;color:inherit;text-align:left;cursor:pointer;font:inherit';
103
+ button.addEventListener('mouseenter', () => { if (!button.disabled)
104
+ button.style.background = 'var(--dsw-alias-interactive-bg-hover,#3a3b3d)'; });
105
+ button.addEventListener('mouseleave', () => { button.style.background = 'transparent'; });
106
+ button.addEventListener('click', () => { closeMenu(); void item.action(); });
107
+ menu.append(button);
108
+ }
109
+ document.body.append(menu);
110
+ const width = menu.offsetWidth || 190;
111
+ const height = menu.offsetHeight || 360;
112
+ menu.style.left = `${Math.max(8, Math.min(x, window.innerWidth - width - 8))}px`;
113
+ menu.style.top = `${Math.max(8, Math.min(y, window.innerHeight - height - 8))}px`;
114
+ }
115
+ function clickEntry(item) {
116
+ ;
117
+ (item.querySelector('button') ?? item).dispatchEvent(new MouseEvent('click', { bubbles: true }));
118
+ }
119
+ async function createEntry(base, directory) {
120
+ const name = window.prompt(directory ? '输入新目录名称或相对路径' : '输入新文件名称或相对路径', '')?.trim();
121
+ if (!name)
122
+ return;
123
+ await runMutation(directory ? 'create-directory' : 'create-file', { path: joinPath(base, name) }, null);
124
+ }
125
+ async function renameEntry(path) {
126
+ const next = window.prompt('输入新的文件名或相对路径', leaf(path))?.trim();
127
+ if (!next || next === leaf(path))
128
+ return;
129
+ const destination = isAbsoluteLike(next) ? next : joinPath(parentPath(path), next);
130
+ await runMutation('rename', { path, destination }, null);
131
+ }
132
+ async function pasteEntry(base, panel) {
133
+ if (clipboard === undefined)
134
+ return;
135
+ await runMutation(clipboard.mode === 'copy' ? 'copy' : 'move', { paths: clipboard.paths, destination: base }, panel);
136
+ if (clipboard.mode === 'cut')
137
+ clipboard = undefined;
138
+ }
139
+ async function deleteEntry(path, panel) {
140
+ if (!window.confirm(`确定删除“${leaf(path)}”吗?`))
141
+ return;
142
+ await runMutation('delete', { paths: [path] }, panel);
143
+ }
144
+ async function downloadFile(target) {
145
+ try {
146
+ const result = await call('download', target);
147
+ const blob = new Blob([decodeBase64(result.contentBase64)], { type: result.mimeType ?? 'application/octet-stream' });
148
+ const link = document.createElement('a');
149
+ link.href = URL.createObjectURL(blob);
150
+ link.download = result.fileName ?? leaf(target.path);
151
+ document.body.append(link);
152
+ link.click();
153
+ link.remove();
154
+ // 某些局域网浏览器在 click 返回后才开始消费 Blob URL,不能立即撤销。
155
+ globalThis.setTimeout(() => URL.revokeObjectURL(link.href), 30_000);
156
+ }
157
+ catch (error) {
158
+ showNotice(errorMessage(error));
159
+ }
160
+ }
161
+ async function copyPath(item, absolute) {
162
+ const path = item.dataset.filesPath ?? '';
163
+ const root = filesRootForEntry(item);
164
+ const value = absolute ? absolutePath(path, root) : relativePath(path, root);
165
+ try {
166
+ await copyTextToClipboard(value);
167
+ showNotice(absolute ? '已复制绝对路径' : '已复制相对路径');
168
+ }
169
+ catch {
170
+ showNotice('复制路径失败');
171
+ }
172
+ }
173
+ async function runMutation(action, payload, panel) {
174
+ try {
175
+ await call(action, payload);
176
+ refreshPanel(panel);
177
+ showNotice('文件操作已完成');
178
+ }
179
+ catch (error) {
180
+ showNotice(errorMessage(error));
181
+ }
182
+ }
183
+ function refreshPanel(panel) {
184
+ const reload = panel?.querySelector('[data-files-reload]')
185
+ ?? findButton(panel, ['重新读取', '重新读取文件'])
186
+ ?? findButton(document, ['重新读取', '重新读取文件']);
187
+ if (reload !== null && reload !== undefined)
188
+ reload.click();
189
+ }
190
+ function enhanceEditors() {
191
+ if (!options.fileEditor)
192
+ return;
193
+ for (const root of document.querySelectorAll('[data-document-preview]')) {
194
+ if (root.dataset.fileManagementEditor === 'true')
195
+ continue;
196
+ const url = root.getAttribute('data-textpreview-url') ?? '';
197
+ if (root.getAttribute('data-textpreview-state') !== 'text' || !isEditableFile(url))
198
+ continue;
199
+ const header = root.querySelector('[data-textpreview-path]')?.parentElement;
200
+ if (header === null || header === undefined)
201
+ continue;
202
+ const button = createEditorButton(root, 'edit', '编辑文件');
203
+ button.setAttribute('data-file-management-edit', 'true');
204
+ button.addEventListener('click', () => void beginEdit(root, url, button));
205
+ placeEditorButton(root, header, button);
206
+ root.dataset.fileManagementEditor = 'true';
207
+ }
208
+ }
209
+ async function beginEdit(root, url, editButton) {
210
+ if (!options.fileEditor)
211
+ return;
212
+ if (editor !== undefined)
213
+ cancelEdit();
214
+ const target = parseFileTarget(url, root);
215
+ if (target === undefined) {
216
+ showNotice('无法解析当前文件路径');
217
+ return;
218
+ }
219
+ try {
220
+ const result = await call('read', target);
221
+ const body = root.querySelector('[data-textpreview-body]');
222
+ if (body === null)
223
+ return;
224
+ const host = document.createElement('div');
225
+ host.setAttribute('data-file-management-editor', 'true');
226
+ host.setAttribute('aria-label', '文件内容编辑器');
227
+ host.style.cssText = 'box-sizing:border-box;width:100%;height:100%;min-height:360px;border:1px solid var(--dsw-alias-border-l2,#666);border-radius:6px;overflow:hidden;background:var(--dsw-alias-bg-layer-1,transparent)';
228
+ body.style.display = 'none';
229
+ body.parentElement?.append(host);
230
+ const language = editorLanguageForPath(target.path);
231
+ const extensions = [basicSetup, editorTheme];
232
+ if (language !== undefined)
233
+ extensions.push(language);
234
+ extensions.push(EditorView.lineWrapping);
235
+ const view = new EditorView({ doc: result.content, extensions, parent: host });
236
+ view.focus();
237
+ const buttons = document.createElement('span');
238
+ buttons.style.cssText = 'display:inline-flex;align-items:center;gap:6px;margin-left:auto';
239
+ const save = createEditorButton(root, 'save', '保存文件');
240
+ const cancel = createEditorButton(root, 'cancel', '取消编辑');
241
+ buttons.append(save, cancel);
242
+ editButton.replaceWith(buttons);
243
+ editor = { root, body, host, view, path: target, buttons };
244
+ save.addEventListener('click', () => void saveEdit());
245
+ cancel.addEventListener('click', () => cancelEdit());
246
+ }
247
+ catch (error) {
248
+ showNotice(errorMessage(error));
249
+ }
250
+ }
251
+ async function saveEdit() {
252
+ if (editor === undefined)
253
+ return;
254
+ try {
255
+ await call('write', { ...editor.path, content: editor.view.state.doc.toString() });
256
+ const reload = editor.root.querySelector('[data-textpreview-tool="reload"]')
257
+ ?? findButton(editor.root.parentElement, ['重新读取文件', '重新读取'])
258
+ ?? findButton(document, ['重新读取文件', '重新读取']);
259
+ cancelEdit();
260
+ reload?.click();
261
+ showNotice('文件已保存');
262
+ }
263
+ catch (error) {
264
+ showNotice(errorMessage(error));
265
+ }
266
+ }
267
+ function cancelEdit(reenhance = true) {
268
+ if (editor === undefined)
269
+ return;
270
+ editor.view.destroy();
271
+ editor.host.remove();
272
+ editor.body.style.display = '';
273
+ editor.buttons.remove();
274
+ editor.root.dataset.fileManagementEditor = '';
275
+ editor = undefined;
276
+ if (reenhance && options.fileEditor)
277
+ enhanceEditors();
278
+ }
279
+ function clearEditorEnhancements() {
280
+ cancelEdit(false);
281
+ for (const root of document.querySelectorAll('[data-document-preview]')) {
282
+ root.querySelector('[data-file-management-edit]')?.remove();
283
+ root.dataset.fileManagementEditor = '';
284
+ }
285
+ }
286
+ async function call(action, payload) {
287
+ return callCodingNsRpc(rpc, `fileManagement/${action}`, payload);
288
+ }
289
+ }
290
+ function placeEditorButton(root, header, button) {
291
+ const openTarget = root.querySelector('[data-open-target="file"]');
292
+ const actionRow = openTarget?.parentElement;
293
+ if (openTarget !== null && openTarget !== undefined && actionRow !== null && actionRow !== undefined && root.contains(openTarget)) {
294
+ actionRow.style.display = 'flex';
295
+ actionRow.style.alignItems = 'center';
296
+ actionRow.style.flexShrink = '0';
297
+ openTarget.style.marginLeft = 'auto';
298
+ openTarget.style.flex = '0 0 auto';
299
+ button.style.marginLeft = '8px';
300
+ button.style.flex = '0 0 32px';
301
+ actionRow.insertBefore(button, openTarget);
302
+ return;
303
+ }
304
+ header.append(button);
305
+ }
306
+ // 编辑器只覆盖文件预览区域,颜色使用 DSH 主题变量,避免切换深浅色时残留固定配色。
307
+ const editorTheme = EditorView.theme({
308
+ '&': {
309
+ height: '100%',
310
+ minHeight: '360px',
311
+ color: 'var(--dsw-alias-label-primary, inherit)',
312
+ backgroundColor: 'var(--dsw-alias-bg-layer-1, transparent)',
313
+ fontSize: '13px',
314
+ },
315
+ '.cm-scroller': {
316
+ overflow: 'auto',
317
+ fontFamily: 'var(--dsw-font-mono, ui-monospace, SFMono-Regular, Menlo, monospace)',
318
+ lineHeight: '1.55',
319
+ },
320
+ '.cm-content': { padding: '16px 0' },
321
+ '.cm-line': { padding: '0 16px' },
322
+ '.cm-gutters': {
323
+ padding: '16px 0',
324
+ backgroundColor: 'var(--dsw-alias-bg-layer-2, transparent)',
325
+ color: 'var(--dsw-alias-label-tertiary, #8a8f98)',
326
+ borderRight: '1px solid var(--dsw-alias-border-l2, #666)',
327
+ },
328
+ '.cm-gutterElement': { minWidth: '2.5em', padding: '0 10px 0 8px' },
329
+ '.cm-activeLine': { backgroundColor: 'var(--dsw-alias-interactive-bg-hover, rgba(127,127,127,.12))' },
330
+ '.cm-activeLineGutter': { backgroundColor: 'var(--dsw-alias-interactive-bg-hover, rgba(127,127,127,.12))' },
331
+ '.cm-selectionBackground, ::selection': { backgroundColor: 'var(--dsw-alias-interactive-bg-selected, rgba(80,120,200,.28)) !important' },
332
+ '.cm-cursor': { borderLeftColor: 'var(--dsw-alias-label-primary, currentColor)' },
333
+ });
334
+ function editorLanguageForPath(path) {
335
+ const extension = path.toLowerCase().split(/[\\/.]/u).pop() ?? '';
336
+ if (extension === 'md' || extension === 'markdown')
337
+ return markdown();
338
+ if (extension === 'json')
339
+ return json();
340
+ if (extension === 'js' || extension === 'jsx' || extension === 'mjs' || extension === 'cjs')
341
+ return javascript({ jsx: extension === 'jsx' });
342
+ if (extension === 'ts' || extension === 'tsx' || extension === 'mts' || extension === 'cts')
343
+ return javascript({ jsx: extension === 'tsx', typescript: true });
344
+ if (extension === 'html' || extension === 'htm' || extension === 'xml')
345
+ return html();
346
+ if (extension === 'css')
347
+ return css();
348
+ if (extension === 'py')
349
+ return python();
350
+ if (extension === 'sql')
351
+ return sql();
352
+ return undefined;
353
+ }
354
+ function createEditorButton(root, iconName, title) {
355
+ const reference = root.querySelector('[data-textpreview-tool]');
356
+ const button = reference === null
357
+ ? document.createElement('button')
358
+ : reference.cloneNode(false);
359
+ button.type = 'button';
360
+ button.textContent = '';
361
+ button.title = title;
362
+ button.setAttribute('aria-label', title);
363
+ button.removeAttribute('data-textpreview-tool');
364
+ button.removeAttribute('disabled');
365
+ button.setAttribute('data-file-management-toolbar-button', 'true');
366
+ button.style.display = 'inline-flex';
367
+ button.style.alignItems = 'center';
368
+ button.style.justifyContent = 'center';
369
+ button.style.boxSizing = 'border-box';
370
+ button.style.width = '32px';
371
+ button.style.minWidth = '32px';
372
+ button.style.height = '32px';
373
+ button.style.marginLeft = '0';
374
+ button.style.padding = '0';
375
+ button.append(createEditorIcon(iconName));
376
+ if (reference === null) {
377
+ button.style.border = '1px solid var(--dsw-alias-border-l2,#d1d5db)';
378
+ button.style.borderRadius = '6px';
379
+ button.style.background = 'var(--dsw-alias-bg-layer-1,transparent)';
380
+ button.style.color = 'var(--dsw-alias-label-primary,inherit)';
381
+ button.style.cursor = 'pointer';
382
+ button.style.transition = 'background-color 120ms ease,border-color 120ms ease,color 120ms ease';
383
+ button.addEventListener('mouseenter', () => {
384
+ button.style.background = 'var(--dsw-alias-interactive-bg-hover,rgba(127,127,127,.12))';
385
+ button.style.borderColor = 'var(--dsw-alias-border-l1,#9ca3af)';
386
+ });
387
+ button.addEventListener('mouseleave', () => {
388
+ button.style.background = 'var(--dsw-alias-bg-layer-1,transparent)';
389
+ button.style.borderColor = 'var(--dsw-alias-border-l2,#d1d5db)';
390
+ });
391
+ }
392
+ return button;
393
+ }
394
+ function createEditorIcon(iconName) {
395
+ const paths = {
396
+ edit: ['M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z', 'm15 5 4 4'],
397
+ save: ['M15.2 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V8.8z', 'M14 3v4a1 1 0 0 1-1 1H7a1 1 0 0 1-1-1V3', 'M6 21v-4a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1v4'],
398
+ cancel: ['M18 6 6 18', 'm6 6 12 12'],
399
+ };
400
+ const createElementNS = typeof document.createElementNS === 'function'
401
+ ? document.createElementNS.bind(document)
402
+ : undefined;
403
+ if (createElementNS !== undefined) {
404
+ const icon = createElementNS('http://www.w3.org/2000/svg', 'svg');
405
+ icon.setAttribute('width', '16');
406
+ icon.setAttribute('height', '16');
407
+ icon.setAttribute('viewBox', '0 0 24 24');
408
+ icon.setAttribute('fill', 'none');
409
+ icon.setAttribute('stroke', 'currentColor');
410
+ icon.setAttribute('stroke-width', '1.8');
411
+ icon.setAttribute('stroke-linecap', 'round');
412
+ icon.setAttribute('stroke-linejoin', 'round');
413
+ icon.setAttribute('aria-hidden', 'true');
414
+ for (const pathData of paths[iconName]) {
415
+ const path = createElementNS('http://www.w3.org/2000/svg', 'path');
416
+ path.setAttribute('d', pathData);
417
+ icon.append(path);
418
+ }
419
+ return icon;
420
+ }
421
+ const fallback = document.createElement('span');
422
+ fallback.textContent = iconName === 'edit' ? '✎' : iconName === 'save' ? '▣' : '×';
423
+ fallback.setAttribute('aria-hidden', 'true');
424
+ fallback.style.fontSize = '16px';
425
+ fallback.style.lineHeight = '1';
426
+ return fallback;
427
+ }
428
+ function findButton(root, labels) {
429
+ if (root === null || root === undefined)
430
+ return undefined;
431
+ for (const button of root.querySelectorAll('button')) {
432
+ const label = button.getAttribute('aria-label')?.trim() || button.textContent?.trim() || '';
433
+ if (labels.includes(label))
434
+ return button;
435
+ }
436
+ return undefined;
437
+ }
438
+ function parseFileTarget(url, root) {
439
+ const match = /^dsh-resource:\/\/file\/session\/([^/]+)\/(.*)$/u.exec(url);
440
+ if (match === null)
441
+ return undefined;
442
+ const sessionId = decodeURIComponent(match[1] ?? '');
443
+ const path = decodeURIComponent(match[2] ?? '');
444
+ const workspaceRoot = root.closest('[data-sidebar-right-panel]')?.querySelector('[data-files-root]')?.getAttribute('data-files-root');
445
+ return workspaceRoot === null || workspaceRoot === undefined || workspaceRoot === ''
446
+ ? { sessionId, path }
447
+ : { path: joinPath(workspaceRoot, path) };
448
+ }
449
+ function filesRootForEntry(item) {
450
+ const tree = item.closest('[data-files-state="tree"]');
451
+ const panelRoot = item.closest('[data-sidebar-right-panel]')?.querySelector('[data-files-root]');
452
+ const value = tree?.getAttribute('data-files-root')?.trim() || panelRoot?.getAttribute('data-files-root')?.trim();
453
+ return value === null || value === undefined || value.trim() === '' ? undefined : value.trim();
454
+ }
455
+ function isEditableFile(url) {
456
+ const path = url.split('/').pop() ?? '';
457
+ const dot = path.lastIndexOf('.');
458
+ return path.startsWith('.gitignore') || (dot >= 0 && TEXT_EXTENSIONS.has(path.slice(dot).toLowerCase()));
459
+ }
460
+ function parentPath(path) { const index = Math.max(path.lastIndexOf('/'), path.lastIndexOf('\\')); return index <= 0 ? path.slice(0, Math.max(index, 1)) : path.slice(0, index); }
461
+ function leaf(path) { return path.split(/[\\/]/u).filter(Boolean).pop() ?? path; }
462
+ function isAbsoluteLike(path) { return path.startsWith('/') || /^[A-Za-z]:[\\/]/u.test(path); }
463
+ function joinPath(base, child) { const separator = base.includes('\\') && !base.includes('/') ? '\\' : '/'; return `${base.replace(/[\\/]$/u, '')}${separator}${child.replace(/^[\\/]+/u, '')}`; }
464
+ function absolutePath(path, root) {
465
+ if (isAbsoluteLike(path) || root === undefined)
466
+ return path;
467
+ return joinPath(root, path);
468
+ }
469
+ function relativePath(path, root) {
470
+ const workspaceRoot = root ?? document.querySelector('[data-files-root]')?.getAttribute('data-files-root') ?? undefined;
471
+ if (workspaceRoot === undefined)
472
+ return path;
473
+ const normalizedPath = path.replaceAll('\\', '/');
474
+ const normalizedRoot = workspaceRoot.replace(/[\\/]$/u, '').replaceAll('\\', '/');
475
+ if (normalizedPath === normalizedRoot)
476
+ return '.';
477
+ if (normalizedPath.startsWith(`${normalizedRoot}/`))
478
+ return normalizedPath.slice(normalizedRoot.length + 1);
479
+ return path;
480
+ }
481
+ async function copyTextToClipboard(text) {
482
+ if (typeof navigator !== 'undefined' && navigator.clipboard?.writeText) {
483
+ try {
484
+ await navigator.clipboard.writeText(text);
485
+ return;
486
+ }
487
+ catch {
488
+ // HTTP 局域网页面可能没有 Clipboard 权限,继续使用兼容回退。
489
+ }
490
+ }
491
+ if (copyTextWithExecCommand(text))
492
+ return;
493
+ throw new Error('浏览器不允许访问剪贴板');
494
+ }
495
+ function copyTextWithExecCommand(text) {
496
+ if (typeof document === 'undefined' || typeof document.execCommand !== 'function' || document.body === null)
497
+ return false;
498
+ const textarea = document.createElement('textarea');
499
+ textarea.value = text;
500
+ textarea.setAttribute('readonly', 'true');
501
+ textarea.style.position = 'fixed';
502
+ textarea.style.top = '-9999px';
503
+ textarea.style.left = '-9999px';
504
+ textarea.style.opacity = '0';
505
+ document.body.append(textarea);
506
+ textarea.focus();
507
+ textarea.select();
508
+ try {
509
+ return document.execCommand('copy');
510
+ }
511
+ catch {
512
+ return false;
513
+ }
514
+ finally {
515
+ textarea.remove();
516
+ }
517
+ }
518
+ function decodeBase64(value) {
519
+ const binary = atob(value);
520
+ const bytes = new Uint8Array(binary.length);
521
+ for (let index = 0; index < binary.length; index += 1)
522
+ bytes[index] = binary.charCodeAt(index);
523
+ return bytes.buffer;
524
+ }
525
+ function errorMessage(error) { return error instanceof Error ? error.message : String(error); }
526
+ function showNotice(message) { const notice = document.createElement('div'); notice.textContent = message; notice.style.cssText = 'position:fixed;z-index:2147483647;left:50%;bottom:24px;transform:translateX(-50%);padding:8px 14px;border-radius:6px;background:#2d2f33;color:#fff;box-shadow:0 4px 18px #0008;font:13px system-ui'; document.body.append(notice); globalThis.setTimeout(() => notice.remove(), 2200); }
527
+ //# sourceMappingURL=file-management-dom.js.map
@@ -348,7 +348,10 @@ function diffLineStyle(kind) {
348
348
  return diffLineBaseStyle;
349
349
  }
350
350
  function HistorySection({ history, totalCount, hasMore, loadingMore, onLoadMore, branches, busy, onSwitch, onCopy, onCopyMessage, onViewDiff, onUndo }) {
351
- const rows = history.map((item, index) => createElement('div', { key: item.commitHash, style: historyRowStyle }, createElement('code', { style: hashStyle }, item.commitHash.slice(0, 8)), createElement('span', { style: fileNameStyle, title: item.subject }, item.subject), createElement('time', { style: mutedStyle }, formatDate(item.authoredAt)), createElement('details', { style: menuStyle }, createElement('summary', { style: menuSummaryStyle, title: '版本操作菜单', 'aria-label': '版本操作菜单' }, '⋯'), createElement('div', { style: menuPopupStyle }, createElement('button', { type: 'button', onClick: () => onViewDiff(item.commitHash), style: menuButtonStyle }, '查看更改文件与 Diff'), createElement('button', { type: 'button', onClick: () => onCopy(item.commitHash), style: menuButtonStyle }, '复制 Commit Hash'), createElement('button', { type: 'button', onClick: () => onCopyMessage(buildCommitMessageText(item.subject, item.body)), style: menuButtonStyle }, '复制提交信息'), createElement('button', { type: 'button', onClick: () => onCopy(item.commitHash), style: menuButtonStyle }, '复制 Git 版本号'), index === 0 ? createElement('button', { type: 'button', disabled: busy, onClick: onUndo, style: dangerMenuButtonStyle }, '撤销上次提交') : null))));
351
+ const rows = groupHistoryByDate(history).flatMap((group) => [
352
+ createElement('div', { key: `date:${group.key}`, style: historyDateHeaderStyle }, createElement('time', { dateTime: group.key, style: historyDateHeaderTextStyle }, group.label)),
353
+ ...group.items.map(({ item, index, timeLabel }) => createElement('div', { key: item.commitHash, style: historyRowStyle }, createElement('code', { style: hashStyle }, item.commitHash.slice(0, 8)), createElement('span', { style: fileNameStyle, title: item.subject }, item.subject), createElement('time', { style: historyTimeStyle, dateTime: item.authoredAt }, timeLabel), createElement('details', { style: menuStyle }, createElement('summary', { style: menuSummaryStyle, title: '版本操作菜单', 'aria-label': '版本操作菜单' }, '⋯'), createElement('div', { style: menuPopupStyle }, createElement('button', { type: 'button', onClick: () => onViewDiff(item.commitHash), style: menuButtonStyle }, '查看更改文件与 Diff'), createElement('button', { type: 'button', onClick: () => onCopy(item.commitHash), style: menuButtonStyle }, '复制 Commit Hash'), createElement('button', { type: 'button', onClick: () => onCopyMessage(buildCommitMessageText(item.subject, item.body)), style: menuButtonStyle }, '复制提交信息'), createElement('button', { type: 'button', onClick: () => onCopy(item.commitHash), style: menuButtonStyle }, '复制 Git 版本号'), index === 0 ? createElement('button', { type: 'button', disabled: busy, onClick: onUndo, style: dangerMenuButtonStyle }, '撤销上次提交') : null)))),
354
+ ]);
352
355
  return createElement('section', { style: sectionStyle }, createElement('div', { style: sectionHeaderStyle }, createElement('strong', undefined, `Git 版本 (${totalCount})`), branches === null ? null : createElement('select', { value: branches.currentBranch, disabled: busy, onChange: (event) => onSwitch(event.currentTarget.value), style: branchSelectStyle }, ...branches.local.map((branch) => createElement('option', { key: branch.name, value: branch.name }, branch.name)))), history.length === 0 ? createElement('div', { style: mutedStyle }, '暂无提交') : rows, hasMore ? createElement('button', { type: 'button', disabled: busy || loadingMore, onClick: onLoadMore, style: loadMoreButtonStyle }, loadingMore ? '正在加载…' : '查看更多版本(每次 100 条)') : null);
353
356
  }
354
357
  function buildChangeTree(items) {
@@ -673,13 +676,26 @@ async function copyText(value) { try {
673
676
  catch {
674
677
  return false;
675
678
  } }
676
- function formatDate(value) {
679
+ function groupHistoryByDate(history) {
680
+ const groups = [];
681
+ for (const [index, item] of history.entries()) {
682
+ const timestamp = formatHistoryTimestamp(item.authoredAt);
683
+ const current = groups.at(-1);
684
+ if (current?.key === timestamp.key) {
685
+ current.items.push({ item, index, timeLabel: timestamp.timeLabel });
686
+ continue;
687
+ }
688
+ groups.push({ key: timestamp.key, label: timestamp.dateLabel, items: [{ item, index, timeLabel: timestamp.timeLabel }] });
689
+ }
690
+ return groups;
691
+ }
692
+ function formatHistoryTimestamp(value) {
677
693
  const timestamp = Date.parse(value);
678
694
  if (Number.isNaN(timestamp))
679
- return value;
680
- const parts = new Intl.DateTimeFormat('zh-CN', { month: 'numeric', day: 'numeric', hour: '2-digit', minute: '2-digit', hourCycle: 'h23' }).formatToParts(timestamp);
695
+ return { key: 'unknown', dateLabel: '未知日期', timeLabel: '未知时间' };
696
+ const parts = new Intl.DateTimeFormat('zh-CN', { year: 'numeric', month: 'numeric', day: 'numeric', hour: '2-digit', minute: '2-digit', hourCycle: 'h23' }).formatToParts(timestamp);
681
697
  const get = (type) => parts.find((part) => part.type === type)?.value ?? '';
682
- return `${get('month')}月${get('day')}日 ${get('hour')}:${get('minute')}`;
698
+ return { key: `${get('year')}-${get('month')}-${get('day')}`, dateLabel: `${get('month')}月${get('day')}日`, timeLabel: `${get('hour')}:${get('minute')}` };
683
699
  }
684
700
  function buildCommitMessageText(subject, body) { const normalizedSubject = subject.trim(); const normalizedBody = body.trim(); return normalizedBody ? `${normalizedSubject}\n\n${normalizedBody}` : normalizedSubject; }
685
701
  const panelStyle = { position: 'relative', boxSizing: 'border-box', display: 'flex', flexDirection: 'column', gap: 12, minHeight: '100%', padding: '16px 18px 24px', overflow: 'auto', background: dshThemeColor.pageBackground, color: dshThemeColor.labelPrimary };
@@ -702,11 +718,14 @@ const fileIconStyle = { width: 12, color: dshThemeColor.labelTertiary, fontSize:
702
718
  const fileNameStyle = { minWidth: 0, flex: 1, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' };
703
719
  const fileStatusStyle = { color: dshThemeColor.labelTertiary, fontFamily: 'monospace', fontSize: 11 };
704
720
  const historyRowStyle = { display: 'flex', alignItems: 'center', gap: 8, minHeight: 28, fontSize: 12 };
721
+ const historyDateHeaderStyle = { paddingTop: 6, color: dshThemeColor.labelSecondary, fontSize: 11, fontWeight: 600 };
722
+ const historyDateHeaderTextStyle = { display: 'block' };
705
723
  const hashStyle = { color: dshThemeColor.labelTertiary, fontSize: 11 };
706
724
  const rowActionsStyle = { display: 'inline-flex', alignItems: 'center', gap: 2, flex: '0 0 auto' };
707
725
  const iconButtonStyle = { display: 'inline-flex', alignItems: 'center', justifyContent: 'center', width: 24, height: 24, border: 0, borderRadius: 4, padding: 0, color: dshThemeColor.labelSecondary, background: 'transparent', cursor: 'pointer', fontSize: 16 };
708
726
  const dangerIconButtonStyle = { ...iconButtonStyle, color: dshThemeColor.error };
709
727
  const mutedStyle = { color: dshThemeColor.labelTertiary, fontSize: 11 };
728
+ const historyTimeStyle = { ...mutedStyle, flex: '0 0 auto', fontVariantNumeric: 'tabular-nums' };
710
729
  const emptyStyle = { padding: 16, color: dshThemeColor.labelSecondary, fontSize: 12 };
711
730
  const diffOverlayStyle = { position: 'fixed', inset: 0, zIndex: 1000, display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 20, boxSizing: 'border-box', background: 'color-mix(in srgb, #000 28%, transparent)' };
712
731
  const diffStyle = { ...sectionStyle, width: 'min(1000px, 100%)', maxHeight: 'min(88vh, 760px)', overflow: 'hidden', boxShadow: dshThemeColor.subtleShadow };
@@ -0,0 +1,29 @@
1
+ import type { AggregateHostResult, AggregateWorkspaceSummary, PeerHostSessionRecord } from '../shared/contracts/peer-host.js';
2
+ export interface HostNavigationSessionItem {
3
+ readonly key: string;
4
+ readonly sessionId: string;
5
+ readonly title: string;
6
+ readonly status: string;
7
+ readonly scope: PeerHostSessionRecord['scope'];
8
+ }
9
+ export interface HostNavigationWorkspaceItem {
10
+ readonly key: string;
11
+ readonly workspaceId: string;
12
+ readonly label: string;
13
+ readonly hostLabel: string;
14
+ readonly availability: AggregateWorkspaceSummary['availability'];
15
+ readonly sessions: readonly HostNavigationSessionItem[];
16
+ }
17
+ export interface HostNavigationHostItem {
18
+ readonly key: string;
19
+ readonly hostId: string;
20
+ readonly targetHostId: string | null;
21
+ readonly label: string;
22
+ readonly availability: AggregateHostResult['availability'];
23
+ readonly errorCode: AggregateHostResult['errorCode'];
24
+ readonly workspaces: readonly HostNavigationWorkspaceItem[];
25
+ }
26
+ /** 将聚合 DTO 转成原生导航可消费的数据;标签不参与资源身份。 */
27
+ export declare function buildHostNavigation(results: readonly AggregateHostResult[]): readonly HostNavigationHostItem[];
28
+ export declare function hostKey(hostId: string, targetHostId: string | null): string;
29
+ //# sourceMappingURL=host-navigation.d.ts.map
@@ -0,0 +1,29 @@
1
+ /** 将聚合 DTO 转成原生导航可消费的数据;标签不参与资源身份。 */
2
+ export function buildHostNavigation(results) {
3
+ return results.map((host) => ({
4
+ key: hostKey(host.hostId, host.targetHostId),
5
+ hostId: host.hostId,
6
+ targetHostId: host.targetHostId,
7
+ label: host.hostLabel,
8
+ availability: host.availability,
9
+ errorCode: host.errorCode,
10
+ workspaces: host.workspaces.map((workspace) => ({
11
+ key: workspace.key,
12
+ workspaceId: workspace.workspaceId,
13
+ label: `${workspace.displayName} (${workspace.hostLabel})`,
14
+ hostLabel: workspace.hostLabel,
15
+ availability: workspace.availability,
16
+ sessions: workspace.sessions.map((session) => ({
17
+ key: `${workspace.key}:${session.scope.sessionId ?? 'none'}`,
18
+ sessionId: session.scope.sessionId ?? '',
19
+ title: session.title,
20
+ status: session.status,
21
+ scope: session.scope,
22
+ })),
23
+ })),
24
+ }));
25
+ }
26
+ export function hostKey(hostId, targetHostId) {
27
+ return `${hostId}:${targetHostId ?? 'current'}`;
28
+ }
29
+ //# sourceMappingURL=host-navigation.js.map
@@ -0,0 +1,3 @@
1
+ export { HostRouter } from '../features/host-router.js';
2
+ export type { HostRouterInput } from '../features/host-router.js';
3
+ //# sourceMappingURL=host-router.d.ts.map
@@ -0,0 +1,2 @@
1
+ export { HostRouter } from '../features/host-router.js';
2
+ //# sourceMappingURL=host-router.js.map