@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 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
- setOpenFile({ path: base, content: "", loading: true });
320
- api("readFile", { profileId: profileId, path: base }).then(function (r) {
321
- if (r && r.ok) setOpenFile({ path: base, content: r.content, binary: r.binary, dirty: false, loading: false });
322
- else setOpenFile({ path: base, content: "", error: (r && r.error) || t("files.errRead"), loading: false });
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
- function saveFile() {
337
- if (!openFile) return;
338
- api("writeFile", { profileId: profileId, path: openFile.path, content: openFile.content }).then(function (r) {
339
- if (r && r.ok) setOpenFile(Object.assign({}, openFile, { dirty: false }));
340
- else setErr((r && r.error) || t("files.errSave"));
341
- });
342
- }
343
-
344
- return h("div", null,
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")),
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: 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,
366
- h("div", { className: "rssh-pathbar" },
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"))
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
- loading ? h("div", { className: "rssh-muted" }, t("common.loading")) : null,
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
- openFile ? h("div", null,
382
- h("div", { className: "rssh-pathbar", style: { marginTop: 8 } },
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.0",
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",