@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.
- package/README.en.md +4 -4
- package/README.md +14 -5
- package/data/build/dist/client/account-bar.js +57 -3
- package/data/build/dist/client/bundle.js +36246 -268
- package/data/build/dist/client/cli-slots.js +60 -18
- package/data/build/dist/client/dsh-h5-bootstrap.d.ts +2 -0
- package/data/build/dist/client/dsh-h5-bootstrap.js +79 -13
- package/data/build/dist/client/features/file-management-panel.d.ts +5 -0
- package/data/build/dist/client/features/file-management-panel.js +48 -0
- package/data/build/dist/client/features/file-management.d.ts +4 -0
- package/data/build/dist/client/features/file-management.js +50 -0
- package/data/build/dist/client/features/index.d.ts +10 -1
- package/data/build/dist/client/features/index.js +12 -1
- package/data/build/dist/client/features/login-protection-panel.js +1 -1
- package/data/build/dist/client/features/login-protection-session.d.ts +2 -0
- package/data/build/dist/client/features/login-protection-session.js +18 -0
- package/data/build/dist/client/features/peer-host.d.ts +4 -0
- package/data/build/dist/client/features/peer-host.js +89 -0
- package/data/build/dist/client/features/reverse-proxy-panel.js +23 -7
- package/data/build/dist/client/features/reverse-proxy.d.ts +1 -2
- package/data/build/dist/client/features/reverse-proxy.js +75 -5
- package/data/build/dist/client/features/types.d.ts +9 -0
- package/data/build/dist/client/features/workspace-session-enhancement-panel.js +10 -0
- package/data/build/dist/client/features/workspace-session-enhancement.js +18 -1
- package/data/build/dist/client/file-management-dom.d.ts +12 -0
- package/data/build/dist/client/file-management-dom.js +527 -0
- package/data/build/dist/client/git-management.js +24 -5
- package/data/build/dist/client/host-navigation.d.ts +29 -0
- package/data/build/dist/client/host-navigation.js +29 -0
- package/data/build/dist/client/host-router.d.ts +3 -0
- package/data/build/dist/client/host-router.js +2 -0
- package/data/build/dist/client/index.d.ts +6 -1
- package/data/build/dist/client/index.js +30 -10
- package/data/build/dist/client/locale.js +36 -7
- package/data/build/dist/client/peer-host-connection-button.d.ts +13 -0
- package/data/build/dist/client/peer-host-connection-button.js +69 -0
- package/data/build/dist/client/peer-host-management-api.d.ts +40 -0
- package/data/build/dist/client/peer-host-management-api.js +33 -0
- package/data/build/dist/client/peer-host-management-panel.d.ts +13 -0
- package/data/build/dist/client/peer-host-management-panel.js +181 -0
- package/data/build/dist/client/peer-host-native-session-ui.d.ts +60 -0
- package/data/build/dist/client/peer-host-native-session-ui.js +372 -0
- package/data/build/dist/client/peer-host-scoped-client.d.ts +67 -0
- package/data/build/dist/client/peer-host-scoped-client.js +316 -0
- package/data/build/dist/client/peer-host-session-controller.d.ts +30 -0
- package/data/build/dist/client/peer-host-session-controller.js +95 -0
- package/data/build/dist/client/quick-phrase-slot.js +14 -0
- package/data/build/dist/client/remote-web-context.js +26 -4
- package/data/build/dist/client/session-changed-files-view.d.ts +15 -0
- package/data/build/dist/client/session-changed-files-view.js +337 -0
- package/data/build/dist/client/settings-bridge.d.ts +14 -4
- package/data/build/dist/client/settings-bridge.js +59 -12
- package/data/build/dist/client/settings-section.js +17 -4
- package/data/build/dist/client/subscription-slot.js +21 -8
- package/data/build/dist/client/workspace-session-rightbar-dom.d.ts +28 -0
- package/data/build/dist/client/workspace-session-rightbar-dom.js +291 -0
- package/data/build/dist/dsh-capabilities/client/config-forms-adapter.js +25 -33
- package/data/build/dist/dsh-capabilities/client/settings-scope-adapter.d.ts +7 -1
- package/data/build/dist/dsh-capabilities/client/settings-scope-adapter.js +34 -11
- package/data/build/dist/dsh-capabilities/matrix.js +9 -0
- package/data/build/dist/dsh-capabilities/routes.js +35 -0
- package/data/build/dist/dsh-capabilities/settings-store.d.ts +12 -0
- package/data/build/dist/dsh-capabilities/settings-store.js +34 -0
- package/data/build/dist/dsh-capabilities/types.d.ts +1 -1
- package/data/build/dist/features/host-router.d.ts +26 -0
- package/data/build/dist/features/host-router.js +110 -0
- package/data/build/dist/features/registry.js +3 -0
- package/data/build/dist/host/cli-adapters/codex-driver.d.ts +13 -1
- package/data/build/dist/host/cli-adapters/codex-driver.js +528 -72
- package/data/build/dist/host/cli-adapters/command-code-driver.d.ts +21 -2
- package/data/build/dist/host/cli-adapters/command-code-driver.js +408 -105
- package/data/build/dist/host/cli-adapters/driver.d.ts +2 -0
- package/data/build/dist/host/cli-adapters/dsh-message-projector.d.ts +2 -0
- package/data/build/dist/host/cli-adapters/dsh-message-projector.js +47 -28
- package/data/build/dist/host/cli-adapters/dsh-tool-history.js +37 -0
- package/data/build/dist/host/cli-adapters/feature.js +94 -10
- package/data/build/dist/host/cli-adapters/json-rpc-process.d.ts +6 -0
- package/data/build/dist/host/cli-adapters/json-rpc-process.js +14 -3
- package/data/build/dist/host/cli-adapters/kimi-driver.js +3 -3
- package/data/build/dist/host/cli-adapters/model-catalog.d.ts +6 -0
- package/data/build/dist/host/cli-adapters/model-catalog.js +8 -0
- package/data/build/dist/host/cli-adapters/opencode-driver.js +14 -2
- package/data/build/dist/host/cli-adapters/pi-driver.js +2 -2
- package/data/build/dist/host/cli-adapters/process-utils.d.ts +8 -0
- package/data/build/dist/host/cli-adapters/process-utils.js +48 -0
- package/data/build/dist/host/cli-adapters/registry.d.ts +6 -1
- package/data/build/dist/host/cli-adapters/registry.js +41 -7
- package/data/build/dist/host/cli-adapters/rpc-driver-utils.js +23 -4
- package/data/build/dist/host/cli-adapters/standard-stream-driver.d.ts +3 -0
- package/data/build/dist/host/cli-adapters/standard-stream-driver.js +36 -15
- package/data/build/dist/host/dsh-device-runtime.d.ts +2 -0
- package/data/build/dist/host/dsh-device-runtime.js +55 -29
- package/data/build/dist/host/dsh-runtime-version.d.ts +3 -2
- package/data/build/dist/host/dsh-runtime-version.js +71 -2
- package/data/build/dist/host/features/auth.js +10 -2
- package/data/build/dist/host/features/file-management.d.ts +5 -0
- package/data/build/dist/host/features/file-management.js +267 -0
- package/data/build/dist/host/features/git-management.js +24 -2
- package/data/build/dist/host/features/index.d.ts +2 -0
- package/data/build/dist/host/features/index.js +6 -0
- package/data/build/dist/host/features/peer-host.d.ts +22 -0
- package/data/build/dist/host/features/peer-host.js +322 -0
- package/data/build/dist/host/features/types.d.ts +4 -0
- package/data/build/dist/host/host-router.d.ts +3 -0
- package/data/build/dist/host/host-router.js +2 -0
- package/data/build/dist/host/index-injection.d.ts +17 -0
- package/data/build/dist/host/index-injection.js +24 -0
- package/data/build/dist/host/index.d.ts +4 -1
- package/data/build/dist/host/index.js +41 -7
- package/data/build/dist/host/lan-access-dsh.d.ts +34 -1
- package/data/build/dist/host/lan-access-dsh.js +145 -26
- package/data/build/dist/host/modules/peer-host/host-api-proxy-service.d.ts +52 -0
- package/data/build/dist/host/modules/peer-host/host-api-proxy-service.js +166 -0
- package/data/build/dist/host/modules/peer-host/host-ws-connector.d.ts +31 -0
- package/data/build/dist/host/modules/peer-host/host-ws-connector.js +108 -0
- package/data/build/dist/host/modules/peer-host/host-ws-proxy-service.d.ts +26 -0
- package/data/build/dist/host/modules/peer-host/host-ws-proxy-service.js +159 -0
- package/data/build/dist/host/modules/peer-host/peer-host-aggregate-service.d.ts +50 -0
- package/data/build/dist/host/modules/peer-host/peer-host-aggregate-service.js +97 -0
- package/data/build/dist/host/modules/peer-host/peer-host-diagnostics.d.ts +19 -0
- package/data/build/dist/host/modules/peer-host/peer-host-diagnostics.js +54 -0
- package/data/build/dist/host/modules/peer-host/peer-host-handshake.d.ts +44 -0
- package/data/build/dist/host/modules/peer-host/peer-host-handshake.js +133 -0
- package/data/build/dist/host/modules/peer-host/peer-host-relay.d.ts +65 -0
- package/data/build/dist/host/modules/peer-host/peer-host-relay.js +196 -0
- package/data/build/dist/host/modules/peer-host/peer-host-session.d.ts +42 -0
- package/data/build/dist/host/modules/peer-host/peer-host-session.js +152 -0
- package/data/build/dist/host/modules/peer-host/peer-host-store.d.ts +92 -0
- package/data/build/dist/host/modules/peer-host/peer-host-store.js +323 -0
- package/data/build/dist/host/modules/peer-host/peer-host-ws-gateway.d.ts +34 -0
- package/data/build/dist/host/modules/peer-host/peer-host-ws-gateway.js +156 -0
- package/data/build/dist/host/native-session-bridge.d.ts +18 -2
- package/data/build/dist/host/native-session-bridge.js +232 -8
- package/data/build/dist/host/remote-web-runtime.js +26 -1
- package/data/build/dist/host/rpc.js +8 -3
- package/data/build/dist/host/session-changes.d.ts +5 -0
- package/data/build/dist/host/session-changes.js +163 -0
- package/data/build/dist/host/settings.js +6 -0
- package/data/build/dist/index.d.ts +1 -1
- package/data/build/dist/index.js +1 -1
- package/data/build/dist/shared/contracts/cli-adapter.d.ts +23 -2
- package/data/build/dist/shared/contracts/config.d.ts +15 -0
- package/data/build/dist/shared/contracts/config.js +10 -0
- package/data/build/dist/shared/contracts/dsh-device.d.ts +2 -0
- package/data/build/dist/shared/contracts/errors.d.ts +14 -0
- package/data/build/dist/shared/contracts/errors.js +15 -0
- package/data/build/dist/shared/contracts/feature.d.ts +2 -0
- package/data/build/dist/shared/contracts/file-management.d.ts +6 -0
- package/data/build/dist/shared/contracts/file-management.js +3 -0
- package/data/build/dist/shared/contracts/peer-host.d.ts +116 -15
- package/data/build/dist/shared/contracts/peer-host.js +19 -0
- package/data/build/dist/shared/contracts/version.d.ts +1 -1
- package/data/build/dist/shared/contracts/version.js +1 -1
- package/data/build/dist/shared/index.d.ts +4 -3
- package/data/build/dist/shared/index.js +2 -2
- package/data/build/dist/transport/debug.d.ts +5 -0
- package/data/build/dist/transport/debug.js +23 -1
- package/data/build/dist/transport/dsh-transport.d.ts +2 -0
- package/data/build/dist/transport/dsh-transport.js +11 -0
- package/data/build/dist/transport/webrtc-client.d.ts +2 -0
- package/data/build/dist/transport/webrtc-client.js +2 -1
- package/data/build/dist/transport/webrtc-host.d.ts +2 -0
- package/data/build/dist/transport/webrtc-host.js +6 -3
- package/data/build/dist/typert.host.d.ts +1 -1
- package/data/build/dist/typert.host.js +2 -1
- package/dsh.bundle.patch +10 -0
- package/package.json +19 -11
- package/profile/README.md +2 -2
- package/profile/package.json +2 -2
- package/profile/version.json +1 -1
- 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
|
|
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
|
|
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
|
|
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')}
|
|
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
|