@linxin666/dsh-ssh 0.2.1 → 0.2.3
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.i18n.yaml +2 -2
- package/README.md +1 -1
- package/README.zh.md +1 -1
- package/lib/client.js +78 -11
- package/lib/client.js.map +1 -1
- package/lib/index.js +7 -1
- package/lib/types/client/index.d.ts +13 -1
- package/lib/types/client/mount.d.ts +3 -1
- package/lib/types/client/panel/SshPanel.d.ts +4 -1
- package/lib/types/client/panel/TerminalTab.d.ts +7 -1
- package/lib/types/client/panel/helpers.d.ts +29 -0
- package/lib/types/index.d.ts +7 -0
- package/package.json +3 -3
- package/src/client/index.ts +38 -3
- package/src/client/mount.tsx +4 -2
- package/src/client/panel/HostsTab.tsx +10 -2
- package/src/client/panel/SshPanel.tsx +5 -3
- package/src/client/panel/TerminalTab.tsx +31 -5
- package/src/client/panel/helpers.ts +44 -0
- package/src/engine/pty.ts +0 -1
- package/src/index.ts +8 -0
package/README.i18n.yaml
CHANGED
|
@@ -2,5 +2,5 @@
|
|
|
2
2
|
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
|
3
3
|
# after editing either side, bring the other along and re-record with:
|
|
4
4
|
# node scripts/verify-docs.mjs --write <dir>
|
|
5
|
-
README.md:
|
|
6
|
-
README.zh.md:
|
|
5
|
+
README.md: f9476df50bac571b567512cfa5272b5a64a1c3d6
|
|
6
|
+
README.zh.md: ec991222c0a38f2376acdd21ae52fe185facd0c2
|
package/README.md
CHANGED
|
@@ -48,7 +48,7 @@ After installing, **restart `dsh web`**: a "SSH" entry appears in the sidebar; t
|
|
|
48
48
|
|
|
49
49
|
## Configuration
|
|
50
50
|
|
|
51
|
-
The settings panel (plugin config) toggles `announceToAgent` (whether to announce the plugin to the Agent) and `enabled` (master switch).
|
|
51
|
+
The settings panel (plugin config) toggles `announceToAgent` (whether to announce the plugin to the Agent) and `enabled` (master switch), and sets `terminalFontFamily` (the web terminal font; empty defers to the CSS chain: `--dsh-ssh-terminal-font` → the official `--ds-font-family-code` token → the built-in monospace stack). The terminal font is fixed in the xterm constructor, so a plain stylesheet cannot override it; to render powerline / Nerd Font glyphs, enter a Nerd Font stack here (e.g. `"SauceCodePro Nerd Font", monospace`). Changes re-apply to open terminals live, no reconnect needed.
|
|
52
52
|
|
|
53
53
|
## Data
|
|
54
54
|
|
package/README.zh.md
CHANGED
|
@@ -48,7 +48,7 @@ dsh plugin --profile web add link:$(pwd)/packages/dsh-ssh
|
|
|
48
48
|
|
|
49
49
|
## 配置
|
|
50
50
|
|
|
51
|
-
设置面板(插件配置)可开关 `announceToAgent`(是否向 Agent 宣告插件)与 `enabled
|
|
51
|
+
设置面板(插件配置)可开关 `announceToAgent`(是否向 Agent 宣告插件)与 `enabled`(总开关),并可设置 `terminalFontFamily`(Web 终端字体,留空则按 CSS 链解析:`--dsh-ssh-terminal-font` → 官方 `--ds-font-family-code` token → 内置 monospace 栈)。终端字体写死在 xterm 构造参数里,CSS 无法直接覆盖;要渲染 powerline / Nerd Font 图标,请在此填入对应 Nerd Font 栈(如 `"SauceCodePro Nerd Font", monospace`),修改对已打开的终端即时生效,无需重连。
|
|
52
52
|
|
|
53
53
|
## 数据
|
|
54
54
|
|
package/lib/client.js
CHANGED
|
@@ -609,6 +609,36 @@ window.__ModuleLoader__.load({
|
|
|
609
609
|
if (error instanceof Error) return error.message;
|
|
610
610
|
return String(error);
|
|
611
611
|
}
|
|
612
|
+
/**
|
|
613
|
+
* The terminal font stack used before issue #577, kept as the last resort
|
|
614
|
+
* when neither the plugin setting nor a CSS custom property names a font.
|
|
615
|
+
*/
|
|
616
|
+
const TERMINAL_FONT_FALLBACK = "Menlo, Consolas, \"Liberation Mono\", monospace";
|
|
617
|
+
/**
|
|
618
|
+
* Resolve the xterm `fontFamily` (issue #577). xterm's DOM renderer takes
|
|
619
|
+
* the font only from constructor/options, so a plain stylesheet rule cannot
|
|
620
|
+
* retarget it — the value must be read back into the options. The chain,
|
|
621
|
+
* first non-empty value wins:
|
|
622
|
+
* 1. `override` — the `terminalFontFamily` plugin setting;
|
|
623
|
+
* 2. `--dsh-ssh-terminal-font` — dedicated hook for skins and user CSS
|
|
624
|
+
* (e.g. a Nerd Font for powerline glyphs);
|
|
625
|
+
* 3. `--ds-font-family-code` — the official code-font token several skins
|
|
626
|
+
* already remap;
|
|
627
|
+
* 4. {@link TERMINAL_FONT_FALLBACK}.
|
|
628
|
+
*/
|
|
629
|
+
function resolveTerminalFontFamily(override) {
|
|
630
|
+
const trimmed = override?.trim();
|
|
631
|
+
if (trimmed !== void 0 && trimmed !== "") return trimmed;
|
|
632
|
+
if (typeof getComputedStyle === "function" && typeof document !== "undefined") {
|
|
633
|
+
const target = document.body ?? document.documentElement;
|
|
634
|
+
const style = getComputedStyle(target);
|
|
635
|
+
for (const name of ["--dsh-ssh-terminal-font", "--ds-font-family-code"]) {
|
|
636
|
+
const value = style.getPropertyValue(name).trim();
|
|
637
|
+
if (value !== "") return value;
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
return TERMINAL_FONT_FALLBACK;
|
|
641
|
+
}
|
|
612
642
|
//#endregion
|
|
613
643
|
//#region \0dsh-css:packages/dsh-ssh/src/client/panel/panel.module.css.mjs
|
|
614
644
|
const css = "[data-pane=conversation],[class*=centerCol]{position:relative}[data-dsh-ssh-view]{z-index:60;background:var(--dsw-alias-bg-base);display:none;position:absolute;inset:0}html[data-dsh-ssh-active]:not([data-dsh-taskboard-active]) [data-dsh-ssh-view]{display:block}html[data-dsh-ssh-active]:not([data-dsh-taskboard-active]) [data-pane=conversation]>:not([data-dsh-ssh-view]),html[data-dsh-ssh-active]:not([data-dsh-taskboard-active]) [class*=centerCol]>:not([data-dsh-ssh-view]){display:none!important}.mL8Uca_entry{width:100%;height:32px;color:var(--dsw-alias-label-secondary);cursor:pointer;white-space:nowrap;background:0 0;border:none;border-radius:8px;align-items:center;gap:8px;padding:0 12px;font-size:13px;display:flex}.mL8Uca_entry:hover{background:var(--dsw-specific-sidebar-nav-item-hover);color:var(--dsw-alias-label-primary)}.mL8Uca_entry[data-active]{background:var(--dsw-specific-sidebar-nav-item-active);color:var(--dsw-alias-label-primary);font-weight:600}.mL8Uca_entryIcon{flex:none;justify-content:center;align-items:center;display:inline-flex}.mL8Uca_entryLabel{text-overflow:ellipsis;overflow:hidden}[data-dsh-frame][data-sidebar-collapsed] .mL8Uca_entry{justify-content:center;width:100%;padding:0}[data-dsh-frame][data-sidebar-collapsed] .mL8Uca_entryLabel{display:none}.mL8Uca_view{overflow:hidden}.mL8Uca_panel{background:var(--dsw-alias-bg-base);min-width:0;height:100%;min-height:0;color:var(--dsw-alias-label-primary);font-family:var(--dsw-font-family);flex-direction:column;gap:10px;padding:14px 16px 16px;display:flex}.mL8Uca_panelHeader{flex:none;align-items:center;gap:10px;display:flex}.mL8Uca_panelTitle{color:var(--dsw-alias-label-primary);white-space:nowrap;flex:1;margin:0;font-size:16px;font-weight:700}.mL8Uca_backButton{align-items:center;gap:4px;display:inline-flex}.mL8Uca_tabBar{border-bottom:1px solid var(--dsw-alias-border-l1);flex:none;gap:2px;display:flex}.mL8Uca_tab{color:var(--dsw-alias-label-secondary);cursor:pointer;white-space:nowrap;background:0 0;border:none;border-bottom:2px solid #0000;border-radius:6px 6px 0 0;padding:7px 14px;font-size:13px}.mL8Uca_tab:hover{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-interactive-bg-hover)}.mL8Uca_tab[data-active]{color:var(--dsw-alias-label-primary);border-bottom-color:var(--dsw-alias-state-business-primary);font-weight:600}.mL8Uca_panelContent{flex-direction:column;flex:1;min-height:0;display:flex;overflow:hidden}.mL8Uca_tabBody{flex-direction:column;flex:1;gap:10px;min-height:0;display:flex;overflow-y:auto}.mL8Uca_fillBody{flex-direction:column;flex:1;gap:10px;min-height:0;display:flex;overflow:hidden}.mL8Uca_toolbar,.mL8Uca_controls{flex-wrap:wrap;flex:none;align-items:center;gap:8px;display:flex}.mL8Uca_controls .mL8Uca_input{flex:0 260px;min-width:150px}.mL8Uca_toolbarSpacer{flex:1}.mL8Uca_search{min-width:120px;color:var(--dsw-alias-label-primary);background:var(--dsw-specific-input-major);border:1px solid var(--dsw-alias-border-l2);border-radius:8px;outline:none;flex:0 260px;padding:6px 10px;font-size:13px}.mL8Uca_search::placeholder{color:var(--dsw-alias-label-tertiary)}.mL8Uca_groupBySelect{color:var(--dsw-alias-label-primary);background:var(--dsw-specific-input-major);border:1px solid var(--dsw-alias-border-l2);border-radius:8px;outline:none;padding:6px 8px;font-size:13px}.mL8Uca_groupSection+.mL8Uca_groupSection{border-top:1px solid var(--dsw-alias-border-l1)}.mL8Uca_groupHeader{background:var(--dsw-alias-bg-base,var(--dsw-specific-input-major));align-items:center;gap:8px;padding:4px 8px;display:flex;position:sticky;top:0}.mL8Uca_groupToggle{min-width:0;color:var(--dsw-alias-label-primary);cursor:pointer;text-align:left;background:0 0;border:none;border-radius:6px;flex:1;align-items:center;gap:6px;padding:4px 6px;font-size:12.5px;font-weight:500;display:flex}.mL8Uca_groupToggle:hover{background:var(--dsw-alias-interactive-bg-hover)}.mL8Uca_groupChevron{border-right:1.5px solid var(--dsw-alias-label-tertiary);border-bottom:1.5px solid var(--dsw-alias-label-tertiary);flex:none;width:7px;height:7px;margin-bottom:2px;transition:transform .12s;transform:rotate(45deg)}.mL8Uca_groupChevron[data-collapsed]{margin-bottom:0;transform:rotate(-45deg)}.mL8Uca_groupName{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.mL8Uca_groupCount{color:var(--dsw-alias-label-tertiary);flex:none;font-weight:400}.mL8Uca_tableWrap{border:1px solid var(--dsw-alias-border-l1);border-radius:10px;flex:1;min-height:0;overflow:auto}.mL8Uca_table{border-collapse:collapse;width:100%;font-size:12.5px}.mL8Uca_table th{z-index:1;text-align:left;background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-secondary);border-bottom:1px solid var(--dsw-alias-border-l1);white-space:nowrap;padding:8px 10px;font-weight:600;position:sticky;top:0}.mL8Uca_table td{border-bottom:1px solid var(--dsw-alias-separator-primary);vertical-align:top;padding:7px 10px}.mL8Uca_table tbody tr:last-child td{border-bottom:none}.mL8Uca_table tbody tr:hover td{background:var(--dsw-alias-interactive-bg-hover)}.mL8Uca_mono{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}.mL8Uca_cellMuted{color:var(--dsw-alias-label-tertiary)}.mL8Uca_actions{white-space:nowrap;align-items:center;gap:8px;display:flex}.mL8Uca_inlineTest{color:var(--dsw-alias-label-secondary);align-items:center;gap:6px;font-size:11.5px;display:inline-flex}.mL8Uca_inlineTest[data-status=ok]{color:var(--dsw-alias-state-success-primary)}.mL8Uca_inlineTest[data-status=fail]{color:var(--dsw-alias-state-error-primary)}.mL8Uca_badge{border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);white-space:nowrap;border-radius:999px;padding:1px 8px;font-size:11px;line-height:1.6;display:inline-block}.mL8Uca_badge[data-kind=key]{color:var(--dsw-alias-state-business-primary);border-color:var(--dsw-alias-state-business-primary)}.mL8Uca_badge[data-kind=password]{color:var(--dsw-alias-state-warn-primary);border-color:var(--dsw-alias-state-warn-primary)}.mL8Uca_badge[data-status=ok]{color:var(--dsw-alias-state-success-primary);border-color:var(--dsw-alias-state-success-primary)}.mL8Uca_badge[data-status=fail]{color:var(--dsw-alias-state-error-primary);border-color:var(--dsw-alias-state-error-primary)}.mL8Uca_badge[data-status=timeout]{color:var(--dsw-alias-state-warn-primary);border-color:var(--dsw-alias-state-warn-primary)}.mL8Uca_primaryButton{color:var(--dsw-alias-label-primary-foreground);background:var(--dsw-alias-button-info-fill);cursor:pointer;white-space:nowrap;border:none;border-radius:8px;padding:6px 14px;font-size:13px;font-weight:600}.mL8Uca_primaryButton:hover:not(:disabled){background:var(--dsw-alias-button-info-hover)}.mL8Uca_primaryButton:disabled{opacity:.5;cursor:default}.mL8Uca_ghostButton{color:var(--dsw-alias-label-primary);border:1px solid var(--dsw-alias-border-l2);cursor:pointer;white-space:nowrap;background:0 0;border-radius:8px;padding:5px 12px;font-size:12px}.mL8Uca_ghostButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}.mL8Uca_ghostButton:disabled{opacity:.45;cursor:default}.mL8Uca_iconButton{width:26px;height:26px;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;border-radius:6px;justify-content:center;align-items:center;padding:0;font-size:13px;display:inline-flex}.mL8Uca_iconButton:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}.mL8Uca_linkButton{color:var(--dsw-alias-state-business-primary);cursor:pointer;white-space:nowrap;background:0 0;border:none;padding:0;font-size:12px}.mL8Uca_linkButton:hover:not(:disabled){text-decoration:underline}.mL8Uca_linkButton:disabled{opacity:.45;cursor:default}.mL8Uca_linkButton[data-danger]{color:var(--dsw-alias-state-error-primary)}.mL8Uca_spinner{border:2px solid var(--dsw-alias-state-business-primary);vertical-align:-1px;border-top-color:#0000;border-radius:50%;flex:none;width:11px;height:11px;animation:.8s linear infinite mL8Uca_dshSshSpin;display:inline-block}@keyframes mL8Uca_dshSshSpin{to{transform:rotate(360deg)}}.mL8Uca_banner{border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);overflow-wrap:anywhere;border-radius:8px;padding:8px 12px;font-size:12.5px;line-height:1.5}.mL8Uca_banner[data-kind=ok]{color:var(--dsw-alias-state-success-primary);border-color:var(--dsw-alias-state-success-primary)}.mL8Uca_banner[data-kind=error]{color:var(--dsw-alias-state-error-primary);border-color:var(--dsw-alias-state-error-primary)}.mL8Uca_banner[data-kind=info]{color:var(--dsw-alias-state-business-primary);border-color:var(--dsw-alias-state-business-primary)}.mL8Uca_empty,.mL8Uca_loading{text-align:center;color:var(--dsw-alias-label-tertiary);padding:28px 12px;font-size:12.5px}.mL8Uca_modalBackdrop{z-index:40;background:var(--dsw-alias-bg-mask-1);justify-content:center;align-items:center;display:flex;position:fixed;inset:0}.mL8Uca_modal{background:var(--dsw-alias-bg-base);border:1px solid var(--dsw-alias-border-l2);width:min(560px,100vw - 48px);max-height:calc(100vh - 96px);box-shadow:var(--dsw-shadow-lv3);color:var(--dsw-alias-label-primary);border-radius:14px;flex-direction:column;gap:12px;padding:18px;display:flex;overflow-y:auto}.mL8Uca_modalTitle{margin:0;font-size:15px;font-weight:700}.mL8Uca_modalFooter{justify-content:flex-end;gap:10px;margin-top:4px;display:flex}.mL8Uca_formError{color:var(--dsw-alias-state-error-primary);margin:0;font-size:12px}.mL8Uca_hint{color:var(--dsw-alias-label-tertiary);font-size:11.5px}.mL8Uca_field{flex-direction:column;gap:5px;display:flex}.mL8Uca_fieldLabel{color:var(--dsw-alias-label-secondary);font-size:12px;font-weight:600}.mL8Uca_input{color:var(--dsw-alias-label-primary);background:var(--dsw-specific-input-major);border:1px solid var(--dsw-alias-border-l2);resize:vertical;border-radius:8px;outline:none;padding:7px 10px;font-family:inherit;font-size:13px}.mL8Uca_input:focus{border-color:var(--dsw-alias-state-business-primary)}.mL8Uca_input::placeholder{color:var(--dsw-alias-label-tertiary)}.mL8Uca_input:disabled{opacity:.55}.mL8Uca_formRow{grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:10px 12px;display:grid}.mL8Uca_radioRow{align-items:center;gap:16px;display:flex}.mL8Uca_radioLabel{color:var(--dsw-alias-label-primary);cursor:pointer;align-items:center;gap:6px;font-size:13px;display:inline-flex}.mL8Uca_termBody{flex-direction:column;flex:1;gap:8px;min-height:0;display:flex;overflow:hidden}.mL8Uca_termWrap{border:1px solid var(--dsw-alias-border-l1);background:#0b0e14;border-radius:10px;flex:1;min-height:0;position:relative;overflow:hidden}.mL8Uca_termContainer{padding:8px 10px;position:absolute;inset:0}.mL8Uca_termPlaceholder{z-index:2;text-align:center;color:var(--dsw-alias-label-tertiary);background:var(--dsw-alias-bg-base);justify-content:center;align-items:center;padding:0 24px;font-size:12.5px;display:flex;position:absolute;inset:0}.mL8Uca_hiddenFile{display:none}.mL8Uca_browsePanel{border:1px solid var(--dsw-alias-border-l1);border-radius:10px;flex-direction:column;flex:none;display:flex;overflow:hidden}.mL8Uca_browseHeader{background:var(--dsw-alias-bg-layer-2);border-bottom:1px solid var(--dsw-alias-border-l1);align-items:center;gap:8px;padding:8px 10px;display:flex}.mL8Uca_browsePath{text-overflow:ellipsis;white-space:nowrap;min-width:0;color:var(--dsw-alias-label-secondary);flex:1;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:12px;overflow:hidden}.mL8Uca_browseList{max-height:240px;overflow-y:auto}.mL8Uca_dirRow{text-align:left;width:100%;font:inherit;color:var(--dsw-alias-label-primary);border:none;border-bottom:1px solid var(--dsw-alias-separator-primary);cursor:pointer;background:0 0;grid-template-columns:minmax(0,1fr) auto auto;gap:12px;padding:5px 10px;font-size:12.5px;display:grid}.mL8Uca_dirRow:hover{background:var(--dsw-alias-interactive-bg-hover)}.mL8Uca_dirRow[data-type=dir] .mL8Uca_dirName{color:var(--dsw-alias-state-business-primary)}.mL8Uca_dirRow[data-up]{color:var(--dsw-alias-label-secondary)}.mL8Uca_dirName{text-overflow:ellipsis;white-space:nowrap;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;overflow:hidden}.mL8Uca_dirType{color:var(--dsw-alias-label-tertiary);white-space:nowrap}.mL8Uca_dirSize{color:var(--dsw-alias-label-tertiary);white-space:nowrap;text-align:right}.mL8Uca_transferBlock{flex-direction:column;flex:none;gap:6px;display:flex}.mL8Uca_progressMeta{color:var(--dsw-alias-label-secondary);flex-wrap:wrap;gap:12px;font-size:12px;display:flex}.mL8Uca_progressTrack{background:var(--dsw-alias-interactive-bg-hover);border-radius:999px;height:8px;overflow:hidden}.mL8Uca_progressBar{background:var(--dsw-alias-state-business-primary);border-radius:999px;height:100%;transition:width .12s linear}.mL8Uca_tunnelList{flex-direction:column;flex:none;gap:8px;display:flex}.mL8Uca_tunnelRow{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);border-radius:10px;align-items:center;gap:10px;padding:8px 12px;display:flex}.mL8Uca_tunnelRow[data-state=forwarding]{border-color:var(--dsw-alias-state-success-primary)}.mL8Uca_tunnelRow[data-state=connecting]{border-color:var(--dsw-alias-state-warn-primary)}.mL8Uca_tunnelRow[data-state=failed]{border-color:var(--dsw-alias-state-error-primary)}.mL8Uca_tunnelLabel{text-overflow:ellipsis;white-space:nowrap;flex:1;min-width:0;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:12.5px;overflow:hidden}.mL8Uca_formCard{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);border-radius:10px;flex-direction:column;flex:none;gap:10px;padding:12px;display:flex}.mL8Uca_clusterForm{flex-direction:column;flex:none;gap:8px;display:flex}.mL8Uca_clusterFilters{grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:10px;display:grid}.mL8Uca_commandInput{min-height:96px;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}.mL8Uca_cellDetails summary{cursor:pointer;color:var(--dsw-alias-label-secondary);font-size:12px}.mL8Uca_cellPre{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);white-space:pre-wrap;word-break:break-all;border-radius:6px;max-height:180px;margin:4px 0 0;padding:6px 8px;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:11.5px;overflow:auto}";
|
|
@@ -1300,15 +1330,19 @@ window.__ModuleLoader__.load({
|
|
|
1300
1330
|
const [collapsed, setCollapsed] = (0, react.useState)({});
|
|
1301
1331
|
const [testingGroup, setTestingGroup] = (0, react.useState)(null);
|
|
1302
1332
|
const seqRef = (0, react.useRef)(0);
|
|
1333
|
+
const mountedRef = (0, react.useRef)(true);
|
|
1334
|
+
(0, react.useEffect)(() => () => {
|
|
1335
|
+
mountedRef.current = false;
|
|
1336
|
+
}, []);
|
|
1303
1337
|
const load = (0, react.useCallback)(async (query) => {
|
|
1304
1338
|
const seq = ++seqRef.current;
|
|
1305
1339
|
try {
|
|
1306
1340
|
const list = await api.listHosts(query);
|
|
1307
|
-
if (seq !== seqRef.current) return;
|
|
1341
|
+
if (!mountedRef.current || seq !== seqRef.current) return;
|
|
1308
1342
|
setHosts(list);
|
|
1309
1343
|
setError(null);
|
|
1310
1344
|
} catch (cause) {
|
|
1311
|
-
if (seq !== seqRef.current) return;
|
|
1345
|
+
if (!mountedRef.current || seq !== seqRef.current) return;
|
|
1312
1346
|
setError(errorMessage(cause));
|
|
1313
1347
|
}
|
|
1314
1348
|
}, [api]);
|
|
@@ -14677,8 +14711,13 @@ window.__ModuleLoader__.load({
|
|
|
14677
14711
|
style.textContent = XTERM_CSS;
|
|
14678
14712
|
document.head.appendChild(style);
|
|
14679
14713
|
}
|
|
14714
|
+
/** No-op source stand-in so the hook order stays stable without the prop. */
|
|
14715
|
+
const NO_FONT_SOURCE = {
|
|
14716
|
+
get: () => void 0,
|
|
14717
|
+
subscribe: () => () => void 0
|
|
14718
|
+
};
|
|
14680
14719
|
/** The xterm terminal view. */
|
|
14681
|
-
function TerminalTab({ api, presetAlias, requestId }) {
|
|
14720
|
+
function TerminalTab({ api, presetAlias, requestId, terminalFont }) {
|
|
14682
14721
|
const [hosts, setHosts] = (0, react.useState)([]);
|
|
14683
14722
|
const [alias, setAlias] = (0, react.useState)(presetAlias ?? "");
|
|
14684
14723
|
const [status, setStatus] = (0, react.useState)({ kind: "idle" });
|
|
@@ -14687,9 +14726,20 @@ window.__ModuleLoader__.load({
|
|
|
14687
14726
|
const fitRef = (0, react.useRef)(null);
|
|
14688
14727
|
const connRef = (0, react.useRef)(null);
|
|
14689
14728
|
const dataSubRef = (0, react.useRef)(null);
|
|
14729
|
+
const fontSource = terminalFont ?? NO_FONT_SOURCE;
|
|
14730
|
+
const fontOverride = (0, react.useSyncExternalStore)(fontSource.subscribe, fontSource.get);
|
|
14690
14731
|
(0, react.useEffect)(() => {
|
|
14691
14732
|
ensureXtermCss();
|
|
14692
14733
|
}, []);
|
|
14734
|
+
(0, react.useEffect)(() => {
|
|
14735
|
+
const term = termRef.current;
|
|
14736
|
+
if (term === null) return;
|
|
14737
|
+
const next = resolveTerminalFontFamily(fontOverride);
|
|
14738
|
+
if (term.options.fontFamily === next) return;
|
|
14739
|
+
term.options.fontFamily = next;
|
|
14740
|
+
fitRef.current?.fit();
|
|
14741
|
+
connRef.current?.resize(term.cols, term.rows);
|
|
14742
|
+
}, [fontOverride]);
|
|
14693
14743
|
(0, react.useEffect)(() => {
|
|
14694
14744
|
let disposed = false;
|
|
14695
14745
|
(async () => {
|
|
@@ -14752,7 +14802,7 @@ window.__ModuleLoader__.load({
|
|
|
14752
14802
|
convertEol: false,
|
|
14753
14803
|
cursorBlink: true,
|
|
14754
14804
|
fontSize: 13,
|
|
14755
|
-
fontFamily:
|
|
14805
|
+
fontFamily: resolveTerminalFontFamily(fontOverride),
|
|
14756
14806
|
theme: {
|
|
14757
14807
|
background: "#0b0e14",
|
|
14758
14808
|
foreground: "#d8dee9",
|
|
@@ -14780,7 +14830,7 @@ window.__ModuleLoader__.load({
|
|
|
14780
14830
|
connection.onOutput = (data) => {
|
|
14781
14831
|
term.write(data);
|
|
14782
14832
|
};
|
|
14783
|
-
connection.onExit = (
|
|
14833
|
+
connection.onExit = (_code, error) => {
|
|
14784
14834
|
if (settled) return;
|
|
14785
14835
|
settled = true;
|
|
14786
14836
|
dataSubRef.current?.dispose();
|
|
@@ -15501,7 +15551,7 @@ window.__ModuleLoader__.load({
|
|
|
15501
15551
|
}
|
|
15502
15552
|
];
|
|
15503
15553
|
/** The tabbed SSH panel. */
|
|
15504
|
-
function SshPanel({ controller, api }) {
|
|
15554
|
+
function SshPanel({ controller, api, terminalFont }) {
|
|
15505
15555
|
const [activeTab, setActiveTab] = (0, react.useState)("hosts");
|
|
15506
15556
|
const [connectRequest, setConnectRequest] = (0, react.useState)(null);
|
|
15507
15557
|
const handleConnect = (alias) => {
|
|
@@ -15558,7 +15608,8 @@ window.__ModuleLoader__.load({
|
|
|
15558
15608
|
activeTab === "terminal" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TerminalTab, {
|
|
15559
15609
|
api,
|
|
15560
15610
|
presetAlias: connectRequest?.alias,
|
|
15561
|
-
requestId: connectRequest?.nonce
|
|
15611
|
+
requestId: connectRequest?.nonce,
|
|
15612
|
+
terminalFont
|
|
15562
15613
|
}),
|
|
15563
15614
|
activeTab === "transfer" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TransferTab, { api }),
|
|
15564
15615
|
activeTab === "tunnels" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TunnelsTab, { api }),
|
|
@@ -15599,9 +15650,10 @@ window.__ModuleLoader__.load({
|
|
|
15599
15650
|
* to the controller's panelOpen state.
|
|
15600
15651
|
* @param controller - the panel controller driving the view.
|
|
15601
15652
|
* @param api - the SSH API client the tabs operate through.
|
|
15653
|
+
* @param terminalFont - live terminal-font setting source (issue #577).
|
|
15602
15654
|
* @returns disposer unmounting the tree and restoring the column.
|
|
15603
15655
|
*/
|
|
15604
|
-
function mountPanel(controller, api) {
|
|
15656
|
+
function mountPanel(controller, api, terminalFont) {
|
|
15605
15657
|
let root;
|
|
15606
15658
|
let container;
|
|
15607
15659
|
const ensure = () => {
|
|
@@ -15622,7 +15674,8 @@ window.__ModuleLoader__.load({
|
|
|
15622
15674
|
root = (0, react_dom_client.createRoot)(container);
|
|
15623
15675
|
root.render(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SshPanel, {
|
|
15624
15676
|
controller,
|
|
15625
|
-
api
|
|
15677
|
+
api,
|
|
15678
|
+
terminalFont
|
|
15626
15679
|
}));
|
|
15627
15680
|
};
|
|
15628
15681
|
const waitObserver = new MutationObserver(() => {
|
|
@@ -15841,8 +15894,14 @@ window.__ModuleLoader__.load({
|
|
|
15841
15894
|
//#region src/client/index.ts
|
|
15842
15895
|
/** Locale namespace this plugin owns. */
|
|
15843
15896
|
const NS = "dsh-ssh";
|
|
15897
|
+
/** Settings namespace the terminal-font preference lives in (issue #577). */
|
|
15898
|
+
const SETTINGS_NS = "dsh-ssh";
|
|
15844
15899
|
/** Required services (fiber inject waiting — the runtime must be up first). */
|
|
15845
|
-
const inject = [
|
|
15900
|
+
const inject = [
|
|
15901
|
+
"slots",
|
|
15902
|
+
"locale",
|
|
15903
|
+
"settingsScope"
|
|
15904
|
+
];
|
|
15846
15905
|
/**
|
|
15847
15906
|
* Mount the SSH panel.
|
|
15848
15907
|
* @param ctx - client root context (locale service).
|
|
@@ -15854,10 +15913,18 @@ window.__ModuleLoader__.load({
|
|
|
15854
15913
|
}), "dsh-ssh: dictionaries");
|
|
15855
15914
|
const controller = new PanelController();
|
|
15856
15915
|
const api = new SshApi();
|
|
15916
|
+
const scope = (ctx.get("webUiSettings") ?? ctx.settingsScope).bind({ namespace: SETTINGS_NS });
|
|
15917
|
+
const terminalFont = {
|
|
15918
|
+
get: () => {
|
|
15919
|
+
const snapshot = scope.getSnapshot();
|
|
15920
|
+
return snapshot.status === "ready" ? snapshot.value?.terminalFontFamily : void 0;
|
|
15921
|
+
},
|
|
15922
|
+
subscribe: (listener) => scope.subscribe(listener)
|
|
15923
|
+
};
|
|
15857
15924
|
const disposers = [];
|
|
15858
15925
|
try {
|
|
15859
15926
|
disposers.push(mountSidebarEntry(controller));
|
|
15860
|
-
disposers.push(mountPanel(controller, api));
|
|
15927
|
+
disposers.push(mountPanel(controller, api, terminalFont));
|
|
15861
15928
|
} catch (error) {
|
|
15862
15929
|
console.warn("[dsh-ssh] mount failed:", error);
|
|
15863
15930
|
}
|