@zhangfengshun/dsh-remote-ssh 1.3.0 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +8 -0
- package/lib/client.js +106 -54
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
本文件的版本号与 `package.json` 的 `version` 保持一致。每个版本对应一个 Cordis Package 快照(`pkg-N`)。
|
|
4
4
|
|
|
5
|
+
## [1.4.0] — 远程文件单开一栏预览
|
|
6
|
+
### 新增
|
|
7
|
+
- 点击「远程文件」文件树中的文件,改为在 better-sidebar 中**单开一栏(独立页签)预览/编辑**,与内置「文件」页签一致,不再在文件树下方内嵌预览。新增隐藏页签类型 `remssh:editor`,同一远程文件按 (profileId, path) 去重复用。
|
|
8
|
+
|
|
9
|
+
## [1.3.1] — 修复远程文件树滚动
|
|
10
|
+
### 修复
|
|
11
|
+
- 「远程文件」页签改为 flex 布局:文件树在文件很多时占据剩余高度并内部滚动(`flex:1; overflow-y:auto`),不再因父容器 `overflow:hidden` 而无法往下拉。
|
|
12
|
+
|
|
5
13
|
## [1.3.0] — 外观跟随主题
|
|
6
14
|
### 新增
|
|
7
15
|
- 远程连接相关界面(设置小节、远程文件 / 终端页签、添加工作区弹窗)的颜色全部改用 DSH 主题 token(`--dsw-alias-*`),自动跟随 DSH 明暗主题(`body[data-ds-dark-theme]`)切换,不再硬编码深色配色。
|
package/lib/client.js
CHANGED
|
@@ -96,6 +96,7 @@ window.__ModuleLoader__.load({
|
|
|
96
96
|
"files.errRead": "读取失败",
|
|
97
97
|
"files.errSave": "保存失败",
|
|
98
98
|
"files.errSaveDefaultDir": "保存默认目录失败",
|
|
99
|
+
"files.binary": "二进制文件,无法预览",
|
|
99
100
|
"term.title": "终端 {id}",
|
|
100
101
|
"term.exited": " (已退出)",
|
|
101
102
|
"term.connecting": "正在连接…",
|
|
@@ -160,6 +161,7 @@ window.__ModuleLoader__.load({
|
|
|
160
161
|
"files.errRead": "Failed to read",
|
|
161
162
|
"files.errSave": "Failed to save",
|
|
162
163
|
"files.errSaveDefaultDir": "Failed to save default directory",
|
|
164
|
+
"files.binary": "Binary file — preview not available",
|
|
163
165
|
"term.title": "Terminal {id}",
|
|
164
166
|
"term.exited": " (exited)",
|
|
165
167
|
"term.connecting": "Connecting…",
|
|
@@ -255,6 +257,7 @@ window.__ModuleLoader__.load({
|
|
|
255
257
|
function FilesTab(props) {
|
|
256
258
|
var t = useT();
|
|
257
259
|
var sessionId = props.sessionId;
|
|
260
|
+
var betterSidebar = props.betterSidebar;
|
|
258
261
|
var [workspaces, setWorkspaces] = React.useState([]);
|
|
259
262
|
var profiles = useProfiles();
|
|
260
263
|
var [wsId, setWsId] = useSessionMemory(sessionId, "wsId", sel.workspaceId);
|
|
@@ -263,7 +266,6 @@ window.__ModuleLoader__.load({
|
|
|
263
266
|
var [entries, setEntries] = useSessionMemory(sessionId, "entries", []);
|
|
264
267
|
var [err, setErr] = React.useState(null);
|
|
265
268
|
var [loading, setLoading] = React.useState(false);
|
|
266
|
-
var [openFile, setOpenFile] = useSessionMemory(sessionId, "openFile", null);
|
|
267
269
|
var [defaultDir, setDefaultDir] = useSessionMemory(sessionId, "defaultDir", "");
|
|
268
270
|
|
|
269
271
|
React.useEffect(function () {
|
|
@@ -316,11 +318,16 @@ window.__ModuleLoader__.load({
|
|
|
316
318
|
setPath(base);
|
|
317
319
|
load(profileId, base);
|
|
318
320
|
} else {
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
321
|
+
// 用独立页签预览远程文件(与内置「文件」页签一致),而非在文件树下方预览。
|
|
322
|
+
if (betterSidebar && betterSidebar.openTab) {
|
|
323
|
+
betterSidebar.openTab({
|
|
324
|
+
type: "remssh:editor",
|
|
325
|
+
id: "remssh:editor:" + profileId + ":" + base,
|
|
326
|
+
title: e.name,
|
|
327
|
+
path: base,
|
|
328
|
+
meta: { profileId: profileId, remotePath: base }
|
|
329
|
+
}, { sessionId: sessionId });
|
|
330
|
+
}
|
|
324
331
|
}
|
|
325
332
|
}
|
|
326
333
|
|
|
@@ -333,62 +340,94 @@ window.__ModuleLoader__.load({
|
|
|
333
340
|
load(profileId, up);
|
|
334
341
|
}
|
|
335
342
|
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
wsId ? h("div", { className: "rssh-row" },
|
|
360
|
-
h("span", { className: "rssh-label" }, t("files.defaultDir")),
|
|
343
|
+
return h("div", { style: { display: "flex", flexDirection: "column", flex: 1, minHeight: 0, overflow: "hidden" } },
|
|
344
|
+
h("div", { style: { flex: "none" } },
|
|
345
|
+
h("div", { className: "rssh-row" },
|
|
346
|
+
h("span", { className: "rssh-label" }, t("files.workspace")),
|
|
347
|
+
h("select", { className: "rssh-select", value: wsId || "", onChange: function (e) { setWsId(e.target.value); setProfileId(""); } },
|
|
348
|
+
h("option", { value: "" }, t("files.selectWorkspace")),
|
|
349
|
+
workspaces.map(function (w) { return h("option", { key: w.id, value: w.id }, w.title); })
|
|
350
|
+
)
|
|
351
|
+
),
|
|
352
|
+
h("div", { className: "rssh-row" },
|
|
353
|
+
h("span", { className: "rssh-label" }, t("files.orProfile")),
|
|
354
|
+
h("select", { className: "rssh-select", value: profileId, onChange: function (e) { setProfileId(e.target.value); setWsId(""); } },
|
|
355
|
+
h("option", { value: "" }, t("common.selectProfile")),
|
|
356
|
+
profiles.map(function (p) { return h("option", { key: p.id, value: p.id }, p.name); })
|
|
357
|
+
)
|
|
358
|
+
),
|
|
359
|
+
wsId ? h("div", { className: "rssh-row" },
|
|
360
|
+
h("span", { className: "rssh-label" }, t("files.defaultDir")),
|
|
361
|
+
h("div", { className: "rssh-pathbar" },
|
|
362
|
+
h("input", { className: "rssh-input", value: defaultDir, onChange: function (e) { setDefaultDir(e.target.value); }, placeholder: "/home/user/project" }),
|
|
363
|
+
h("button", { className: "rssh-btn", onClick: saveDefaultDir }, t("common.save"))
|
|
364
|
+
)
|
|
365
|
+
) : null,
|
|
361
366
|
h("div", { className: "rssh-pathbar" },
|
|
362
|
-
h("input", { className: "rssh-input", value:
|
|
363
|
-
h("button", { className: "rssh-btn", onClick:
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
h("
|
|
368
|
-
h("button", { className: "rssh-btn", onClick: function () { load(profileId, path); } }, t("common.open")),
|
|
369
|
-
h("button", { className: "rssh-btn", onClick: goUp }, t("common.up"))
|
|
367
|
+
h("input", { className: "rssh-input", value: path, onChange: function (e) { setPath(e.target.value); }, placeholder: "/home/user" }),
|
|
368
|
+
h("button", { className: "rssh-btn", onClick: function () { load(profileId, path); } }, t("common.open")),
|
|
369
|
+
h("button", { className: "rssh-btn", onClick: goUp }, t("common.up"))
|
|
370
|
+
),
|
|
371
|
+
loading ? h("div", { className: "rssh-muted" }, t("common.loading")) : null,
|
|
372
|
+
err ? h("div", { className: "rssh-err", style: { whiteSpace: "pre-wrap" } }, err) : null
|
|
370
373
|
),
|
|
371
|
-
|
|
372
|
-
err ? h("div", { className: "rssh-err", style: { whiteSpace: "pre-wrap" } }, err) : null,
|
|
373
|
-
h("div", { style: { marginTop: 6 } },
|
|
374
|
+
h("div", { style: { flex: 1, minHeight: 0, overflowY: "auto", marginTop: 6 } },
|
|
374
375
|
entries.map(function (e) {
|
|
375
376
|
return h("div", { key: e.name, className: "rssh-tree-item", onClick: function () { openEntry(e); } },
|
|
376
377
|
h("span", { className: e.type === "directory" ? "rssh-tree-dir" : "rssh-tree-file" }, e.type === "directory" ? "📁 " + e.name : "📄 " + e.name),
|
|
377
378
|
e.type === "file" ? h("span", { className: "rssh-tree-size" }, String(e.size)) : null
|
|
378
379
|
);
|
|
379
380
|
})
|
|
381
|
+
)
|
|
382
|
+
);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// ======================================================================
|
|
386
|
+
// 远程文件编辑器页签:点击文件树中的文件后单开一栏预览/编辑(与内置「文件」页签一致)
|
|
387
|
+
// ======================================================================
|
|
388
|
+
function RemoteEditor(props) {
|
|
389
|
+
var t = useT();
|
|
390
|
+
var tab = props.tab || {};
|
|
391
|
+
var ctx = props.ctx;
|
|
392
|
+
var meta = tab.meta || {};
|
|
393
|
+
var profileId = meta.profileId || "";
|
|
394
|
+
var remotePath = tab.path || meta.remotePath || "";
|
|
395
|
+
var [data, setData] = React.useState({ loading: true, content: "", binary: false, dirty: false, error: null });
|
|
396
|
+
|
|
397
|
+
React.useEffect(function () {
|
|
398
|
+
var alive = true;
|
|
399
|
+
setData({ loading: true, content: "", binary: false, dirty: false, error: null });
|
|
400
|
+
api("readFile", { profileId: profileId, path: remotePath }).then(function (r) {
|
|
401
|
+
if (!alive) return;
|
|
402
|
+
if (r && r.ok) setData({ loading: false, content: r.content || "", binary: !!r.binary, dirty: false, error: null });
|
|
403
|
+
else setData({ loading: false, content: "", binary: false, dirty: false, error: (r && r.error) || t("files.errRead") });
|
|
404
|
+
});
|
|
405
|
+
return function () { alive = false; };
|
|
406
|
+
}, [profileId, remotePath]);
|
|
407
|
+
|
|
408
|
+
function save() {
|
|
409
|
+
api("writeFile", { profileId: profileId, path: remotePath, content: data.content }).then(function (r) {
|
|
410
|
+
if (r && r.ok) setData(Object.assign({}, data, { dirty: false, error: null }));
|
|
411
|
+
else setData(Object.assign({}, data, { error: (r && r.error) || t("files.errSave") }));
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
function close() {
|
|
416
|
+
if (ctx && ctx.betterSidebar && tab.id) ctx.betterSidebar.closeTab(tab.id);
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
if (data.loading) return h("div", { className: "rssh-muted", style: { padding: 8 } }, t("files.reading"));
|
|
420
|
+
if (data.error) return h("div", { className: "rssh-err", style: { padding: 8, whiteSpace: "pre-wrap" } }, data.error);
|
|
421
|
+
if (data.binary) return h("div", { className: "rssh-muted", style: { padding: 8 } }, t("files.binary"));
|
|
422
|
+
|
|
423
|
+
return h("div", { style: { display: "flex", flexDirection: "column", flex: 1, minHeight: 0, padding: 8, overflow: "hidden" } },
|
|
424
|
+
h("div", { className: "rssh-pathbar", style: { flex: "none" } },
|
|
425
|
+
h("span", { className: "rssh-title", style: { flex: 1, wordBreak: "break-all" } }, remotePath),
|
|
426
|
+
h("button", { className: "rssh-btn", onClick: save }, t("common.save")),
|
|
427
|
+
h("button", { className: "rssh-btn", onClick: close }, t("common.close"))
|
|
380
428
|
),
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
h("span", { className: "rssh-title", style: { flex: 1, wordBreak: "break-all" } }, openFile.path),
|
|
384
|
-
h("button", { className: "rssh-btn", onClick: saveFile }, t("common.save")),
|
|
385
|
-
h("button", { className: "rssh-btn", onClick: function () { setOpenFile(null); } }, t("common.close"))
|
|
386
|
-
),
|
|
387
|
-
openFile.loading ? h("div", { className: "rssh-muted" }, t("files.reading")) :
|
|
388
|
-
openFile.error ? h("div", { className: "rssh-err" }, openFile.error) :
|
|
389
|
-
h("textarea", { className: "rssh-content", style: { width: "100%", boxSizing: "border-box", minHeight: 200, background: "var(--dsw-alias-markdown-code-block)", color: "var(--dsw-alias-label-primary)", border: "1px solid var(--dsw-alias-border-l2)" },
|
|
390
|
-
value: openFile.content, onChange: function (e) { setOpenFile(Object.assign({}, openFile, { content: e.target.value, dirty: true })); } })
|
|
391
|
-
) : null
|
|
429
|
+
h("textarea", { className: "rssh-content", style: { flex: 1, minHeight: 0, maxHeight: "none", width: "100%", boxSizing: "border-box", background: "var(--dsw-alias-markdown-code-block)", color: "var(--dsw-alias-label-primary)", border: "1px solid var(--dsw-alias-border-l2)" },
|
|
430
|
+
value: data.content, onChange: function (e) { setData(Object.assign({}, data, { content: e.target.value, dirty: true })); } })
|
|
392
431
|
);
|
|
393
432
|
}
|
|
394
433
|
|
|
@@ -726,7 +765,20 @@ window.__ModuleLoader__.load({
|
|
|
726
765
|
icon: function () { return h("span", null, "🗂️"); },
|
|
727
766
|
component: function (props) {
|
|
728
767
|
var sid = props && props.scope ? props.scope.sessionId : null;
|
|
729
|
-
return h(FilesTab, { key: "files:" + (sid || "global"), sessionId: sid });
|
|
768
|
+
return h(FilesTab, { key: "files:" + (sid || "global"), sessionId: sid, betterSidebar: props && props.ctx ? props.ctx.betterSidebar : null });
|
|
769
|
+
}
|
|
770
|
+
});
|
|
771
|
+
});
|
|
772
|
+
// 远程文件编辑器页签(隐藏页签,仅由文件树点击打开,每个远程文件单开一栏)。
|
|
773
|
+
ctx.effect(function () {
|
|
774
|
+
return betterSidebar.registerTab({
|
|
775
|
+
id: "remssh:editor",
|
|
776
|
+
title: function () { return i18n.t("tabs.files"); },
|
|
777
|
+
hidden: true,
|
|
778
|
+
dedupeKey: function (tab) { return (tab && tab.meta && tab.meta.profileId || "") + "\u0000" + (tab && tab.path || ""); },
|
|
779
|
+
icon: function () { return h("span", null, "📄"); },
|
|
780
|
+
component: function (props) {
|
|
781
|
+
return h(RemoteEditor, { tab: props && props.tab, ctx: props && props.ctx, scope: props && props.scope });
|
|
730
782
|
}
|
|
731
783
|
});
|
|
732
784
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zhangfengshun/dsh-remote-ssh",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"description": "DSH web plugin: VSCode Remote-SSH-like remote development (SSH to supercomputers/servers, remote workspace, file explorer, integrated terminal), integrated with dsh-better-sidebar and DSH settings.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"dsh",
|