@xiaoso/dsh-tool-plus 0.1.0-beta.6 → 0.1.1
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 +9 -5
- package/README.md +9 -5
- package/lib/client.js +372 -120
- package/lib/index.mjs +300 -16
- package/lib/{scrapers-BlWQMGjU.mjs → scrapers-DI8yWlu_.mjs} +1 -1
- package/lib/trash-cli.mjs +281 -0
- package/lib/{utils-D1QPFbRk.mjs → utils-D1p8-H8L.mjs} +1 -1
- package/package.json +148 -147
- package/presets/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -75,6 +75,15 @@ window.__ModuleLoader__.load({
|
|
|
75
75
|
group: "behavior",
|
|
76
76
|
tool: "bash"
|
|
77
77
|
},
|
|
78
|
+
{
|
|
79
|
+
name: "rmSafe",
|
|
80
|
+
kind: "boolean",
|
|
81
|
+
default: true,
|
|
82
|
+
labelKey: "rmSafe",
|
|
83
|
+
hintKey: "rmSafeHint",
|
|
84
|
+
group: "behavior",
|
|
85
|
+
tool: "bash"
|
|
86
|
+
},
|
|
78
87
|
{
|
|
79
88
|
name: "useShellCommandWrapper",
|
|
80
89
|
kind: "boolean",
|
|
@@ -726,57 +735,83 @@ window.__ModuleLoader__.load({
|
|
|
726
735
|
TOOL_PLUS_FIELDS.map((field) => field.name);
|
|
727
736
|
//#endregion
|
|
728
737
|
//#region src/client/rows.tsx
|
|
729
|
-
/**
|
|
738
|
+
/**
|
|
739
|
+
* Shared settings-row controls for the tool-plus settings surfaces: number
|
|
740
|
+
* input, select pill, and toggle switch rows, styled against the app's
|
|
741
|
+
* `--dsw-alias-*` design tokens. Extracted from the Bash card so the Plugins
|
|
742
|
+
* card and the Tool Plus settings section render identical controls.
|
|
743
|
+
* @module @xiaoso/dsh-tool-plus/client
|
|
744
|
+
*/
|
|
745
|
+
/**
|
|
746
|
+
* Shared row control CSS, keyed by `data-plugin-css` and injected once.
|
|
747
|
+
*
|
|
748
|
+
* Motion discipline: every moving control animates transform/opacity only
|
|
749
|
+
* (GPU-composited); color transitions fire on hover/focus state changes
|
|
750
|
+
* only. Everything is disabled under prefers-reduced-motion. All colors are
|
|
751
|
+
* `--dsw-alias-*` host tokens (or color-mix of them), so light/dark themes
|
|
752
|
+
* adapt automatically.
|
|
753
|
+
*/
|
|
730
754
|
const SETTINGS_ROWS_CSS = `
|
|
731
|
-
.tp-row{display:flex;align-items:center;gap:12px;padding:12px 0;border-bottom:1px solid var(--dsw-alias-border-
|
|
755
|
+
.tp-row{display:flex;align-items:center;gap:12px;padding:12px 0;border-bottom:1px solid var(--dsw-alias-border-l1);transition:background .16s ease}
|
|
732
756
|
.tp-row:last-child{border-bottom:none}
|
|
733
757
|
.tp-rowText{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px;padding-right:24px}
|
|
734
758
|
.tp-rowTitle{font-size:14px;font-weight:400;line-height:22px;color:var(--dsw-alias-label-primary)}
|
|
735
759
|
.tp-rowDesc{font-size:12px;font-weight:400;line-height:18px;color:var(--dsw-alias-label-tertiary)}
|
|
736
760
|
.tp-badges{display:inline-flex;align-items:center;gap:8px}
|
|
737
|
-
.tp-badge{
|
|
738
|
-
.tp-
|
|
761
|
+
.tp-badge{display:inline-flex;align-items:center;gap:6px;padding:1px 0;font-size:11px;line-height:17px;white-space:nowrap;font-weight:500;color:var(--dsw-alias-label-tertiary)}
|
|
762
|
+
.tp-badge::before{content:'';flex:none;width:5px;height:5px;border-radius:999px;background:var(--dsw-alias-state-business-primary)}
|
|
763
|
+
.tp-reset{position:relative;border:none;background:none;padding:2px 0;font:inherit;font-size:12px;line-height:1.5;color:var(--dsw-alias-label-secondary);cursor:pointer;transition:color .16s ease}
|
|
764
|
+
.tp-reset::after{content:'';position:absolute;left:0;right:0;bottom:0;height:1px;border-radius:1px;background:currentColor;opacity:0;transform:scaleX(.6);transform-origin:left center;transition:opacity .16s ease,transform .2s cubic-bezier(.22,1,.36,1)}
|
|
739
765
|
.tp-reset:hover:not(:disabled){color:var(--dsw-alias-label-primary)}
|
|
766
|
+
.tp-reset:hover:not(:disabled)::after{opacity:.6;transform:scaleX(1)}
|
|
740
767
|
.tp-reset:disabled{cursor:default}
|
|
741
|
-
.tp-
|
|
742
|
-
.tp-
|
|
743
|
-
.tp-
|
|
744
|
-
.tp-
|
|
745
|
-
.tp-input{height:34px;padding:0 12px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;background:var(--dsw-alias-bg-layer-3);font:inherit;font-size:13px;line-height:1.5;color:var(--dsw-alias-label-primary);width:100%;box-sizing:border-box}
|
|
746
|
-
.tp-input:focus-visible{outline:none;border-color:var(--dsw-alias-brand-primary)}
|
|
768
|
+
.tp-reset:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:2px;border-radius:2px}
|
|
769
|
+
.tp-input{flex:none;width:128px;height:34px;padding:0 11px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;background:var(--dsw-alias-bg-layer-1);font:inherit;font-size:13px;line-height:1.5;color:var(--dsw-alias-label-primary);box-sizing:border-box;transition:border-color .16s ease}
|
|
770
|
+
.tp-input:hover:not(:disabled):not(:focus-visible){border-color:var(--dsw-alias-border-l3)}
|
|
771
|
+
.tp-input:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:1px;border-color:var(--dsw-alias-border-l3)}
|
|
747
772
|
.tp-input:disabled{color:var(--dsw-alias-label-tertiary);cursor:default}
|
|
748
773
|
.tp-input.tp-invalid{border-color:var(--dsw-alias-label-error)}
|
|
749
|
-
.tp-
|
|
750
|
-
.tp-
|
|
751
|
-
.tp-selectTrigger{display:inline-flex;align-items:center;gap:12px;height:36px;padding:0 14px;border:none;border-radius:18px;background:var(--dsw-alias-bg-module-platform);font:inherit;font-size:14px;line-height:22px;color:var(--dsw-alias-label-primary);cursor:pointer;flex-shrink:0}
|
|
774
|
+
.tp-input.tp-invalid:focus-visible{outline-color:var(--dsw-alias-state-error-primary)}
|
|
775
|
+
.tp-invalidText{color:var(--dsw-alias-label-error);animation:tp-soft-in .18s ease both}
|
|
752
776
|
.tp-selectAnchor{display:inline-flex;flex-shrink:0}
|
|
753
|
-
.tp-selectTrigger:
|
|
754
|
-
.tp-selectTrigger:disabled{
|
|
755
|
-
.tp-selectTrigger:
|
|
756
|
-
.tp-
|
|
777
|
+
.tp-selectTrigger{display:inline-flex;align-items:center;gap:8px;height:34px;padding:0 14px;border:1px solid var(--dsw-alias-border-l2);border-radius:999px;background:var(--dsw-alias-bg-module-platform);font:inherit;font-size:13px;line-height:20px;color:var(--dsw-alias-label-primary);cursor:pointer;flex-shrink:0;transition:background .16s ease,border-color .16s ease,transform .1s ease}
|
|
778
|
+
.tp-selectTrigger:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover);border-color:var(--dsw-alias-border-l3)}
|
|
779
|
+
.tp-selectTrigger:active:not(:disabled){transform:scale(.97)}
|
|
780
|
+
.tp-selectTrigger:disabled{opacity:.55;cursor:default}
|
|
781
|
+
.tp-selectTrigger:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:1px}
|
|
782
|
+
.tp-selectChevron{flex:none;color:var(--dsw-alias-label-tertiary);display:inline-flex;transition:transform .2s cubic-bezier(.22,1,.36,1)}
|
|
757
783
|
.tp-selectTrigger[aria-expanded="true"] .tp-selectChevron{transform:rotate(180deg)}
|
|
758
|
-
.tp-switch{position:relative;width:
|
|
759
|
-
.tp-switch input{position:absolute;opacity:0;width:100%;height:100%;margin:0;cursor:pointer}
|
|
760
|
-
.tp-switchTrack{position:absolute;inset:0;border-radius:999px;background:var(--dsw-alias-border-l2);transition:background .
|
|
784
|
+
.tp-switch{position:relative;width:40px;height:24px;flex:none}
|
|
785
|
+
.tp-switch input{position:absolute;opacity:0;width:100%;height:100%;margin:0;cursor:pointer;z-index:1}
|
|
786
|
+
.tp-switchTrack{position:absolute;inset:0;border-radius:999px;background:var(--dsw-alias-border-l2);transition:background .18s ease;pointer-events:none}
|
|
787
|
+
.tp-row:hover .tp-switchTrack{background:var(--dsw-alias-border-l3)}
|
|
761
788
|
.tp-switch input:checked+.tp-switchTrack{background:var(--dsw-alias-brand-primary)}
|
|
762
|
-
.tp-switch input:
|
|
763
|
-
.tp-
|
|
764
|
-
.tp-
|
|
765
|
-
.tp-
|
|
789
|
+
.tp-row:hover .tp-switch input:checked+.tp-switchTrack{background:var(--dsw-alias-brand-primary)}
|
|
790
|
+
.tp-switch input:focus-visible+.tp-switchTrack{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:2px}
|
|
791
|
+
.tp-thumb{position:absolute;top:3px;left:3px;width:18px;height:18px;border-radius:999px;background:var(--dsw-alias-bg-layer-3);box-shadow:0 1px 3px rgba(0,0,0,.14),0 1px 1px rgba(0,0,0,.08);transition:transform .2s cubic-bezier(.34,1.3,.5,1);transform-origin:left center;pointer-events:none}
|
|
792
|
+
.tp-switch input:checked~.tp-thumb{transform:translateX(16px);transform-origin:right center}
|
|
793
|
+
.tp-switch input:active:not(:disabled)~.tp-thumb{transform:scaleX(1.2)}
|
|
794
|
+
.tp-switch input:checked:active:not(:disabled)~.tp-thumb{transform:translateX(16px) scaleX(1.2)}
|
|
795
|
+
.tp-switch input:disabled{cursor:default}
|
|
796
|
+
.tp-switch input:disabled~.tp-switchTrack,.tp-switch input:disabled~.tp-thumb{opacity:.45}
|
|
797
|
+
.tp-actionField{display:flex;flex-direction:column;gap:8px;padding:12px 0;border-bottom:1px solid var(--dsw-alias-border-l1)}
|
|
766
798
|
.tp-actionField:last-child{border-bottom:none}
|
|
767
799
|
.tp-actionHead{display:flex;align-items:center;gap:12px}
|
|
768
800
|
.tp-actionInfo{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px}
|
|
769
801
|
.tp-actionTitle{font-size:14px;font-weight:400;line-height:22px;color:var(--dsw-alias-label-primary)}
|
|
770
802
|
.tp-actionDesc{font-size:12px;font-weight:400;line-height:18px;color:var(--dsw-alias-label-tertiary)}
|
|
771
|
-
.tp-actionButton{appearance:none;flex:none;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;background:var(--dsw-alias-bg-layer-
|
|
772
|
-
.tp-actionButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}
|
|
803
|
+
.tp-actionButton{appearance:none;flex:none;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;background:var(--dsw-alias-bg-layer-1);padding:5px 14px;font:inherit;font-size:13px;line-height:1.5;color:var(--dsw-alias-label-primary);cursor:pointer;transition:background .16s ease,border-color .16s ease,transform .1s ease}
|
|
804
|
+
.tp-actionButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover);border-color:var(--dsw-alias-border-l3)}
|
|
805
|
+
.tp-actionButton:active:not(:disabled){transform:scale(.97)}
|
|
773
806
|
.tp-actionButton:disabled{opacity:.55;cursor:default}
|
|
774
|
-
.tp-actionButton:focus-visible{outline:2px solid var(--dsw-alias-
|
|
775
|
-
.tp-actionResult{margin:0;font-size:12px;line-height:1.5;white-space:pre-wrap;overflow-wrap:anywhere}
|
|
807
|
+
.tp-actionButton:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:1px}
|
|
808
|
+
.tp-actionResult{margin:0;font-size:12px;line-height:1.5;white-space:pre-wrap;overflow-wrap:anywhere;animation:tp-soft-in .22s cubic-bezier(.22,1,.36,1) both}
|
|
776
809
|
.tp-actionResult[data-state="ok"]{color:var(--dsw-alias-label-secondary)}
|
|
777
810
|
.tp-actionResult[data-state="error"]{color:var(--dsw-alias-label-error)}
|
|
811
|
+
@keyframes tp-soft-in{from{opacity:0;transform:translateY(4px)}to{opacity:1;transform:none}}
|
|
778
812
|
@media (prefers-reduced-motion: reduce){
|
|
779
|
-
.tp-
|
|
813
|
+
.tp-row,.tp-reset,.tp-reset::after,.tp-input,.tp-selectTrigger,.tp-selectChevron,.tp-switchTrack,.tp-thumb,.tp-actionButton{transition:none}
|
|
814
|
+
.tp-invalidText,.tp-actionResult{animation:none}
|
|
780
815
|
}
|
|
781
816
|
`;
|
|
782
817
|
/** Inject the shared row stylesheet once per page. */
|
|
@@ -795,46 +830,51 @@ window.__ModuleLoader__.load({
|
|
|
795
830
|
function hasOwn(obj, key) {
|
|
796
831
|
return typeof obj === "object" && obj !== null && Object.prototype.hasOwnProperty.call(obj, key);
|
|
797
832
|
}
|
|
798
|
-
/**
|
|
833
|
+
/**
|
|
834
|
+
* Number row: same left-text / right-control anatomy as toggle and select
|
|
835
|
+
* rows, with the input right-aligned at a fixed width. The title is wired to
|
|
836
|
+
* the input (htmlFor), so clicking the title focuses the input; the
|
|
837
|
+
* description and the rest of the row are inert — each side manages its own
|
|
838
|
+
* interaction.
|
|
839
|
+
*/
|
|
799
840
|
function NumberRow(props) {
|
|
800
841
|
const { t, control, disabled, onEdit, onReset } = props;
|
|
842
|
+
const inputId = (0, react.useId)();
|
|
801
843
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
802
|
-
className: "tp-
|
|
844
|
+
className: "tp-row",
|
|
803
845
|
children: [
|
|
804
846
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
805
|
-
className: "tp-
|
|
847
|
+
className: "tp-rowText",
|
|
806
848
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
|
|
807
|
-
className: "tp-
|
|
849
|
+
className: "tp-rowTitle",
|
|
850
|
+
htmlFor: inputId,
|
|
808
851
|
children: t(control.labelKey)
|
|
809
|
-
}),
|
|
810
|
-
className: "tp-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
815
|
-
type: "button",
|
|
816
|
-
className: "tp-reset",
|
|
817
|
-
disabled,
|
|
818
|
-
onClick: () => onReset(control.field),
|
|
819
|
-
children: t("reset")
|
|
820
|
-
})]
|
|
821
|
-
}) : null]
|
|
852
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
853
|
+
className: "tp-rowDesc" + (control.invalid ? " tp-invalidText" : ""),
|
|
854
|
+
role: control.invalid ? "status" : void 0,
|
|
855
|
+
children: control.invalid ? t("invalidNumber") : t(control.hintKey)
|
|
856
|
+
})]
|
|
822
857
|
}),
|
|
858
|
+
control.overridden ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
859
|
+
className: "tp-badges",
|
|
860
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
861
|
+
className: "tp-badge",
|
|
862
|
+
children: t("overridden")
|
|
863
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
864
|
+
type: "button",
|
|
865
|
+
className: "tp-reset",
|
|
866
|
+
disabled,
|
|
867
|
+
onClick: () => onReset(control.field),
|
|
868
|
+
children: t("reset")
|
|
869
|
+
})]
|
|
870
|
+
}) : null,
|
|
823
871
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
872
|
+
id: inputId,
|
|
824
873
|
type: "number",
|
|
825
|
-
"aria-label": t(control.labelKey),
|
|
826
874
|
className: "tp-input" + (control.invalid ? " tp-invalid" : ""),
|
|
827
875
|
disabled,
|
|
828
876
|
value: control.text,
|
|
829
877
|
onChange: (e) => onEdit(control.field, e.target.value)
|
|
830
|
-
}),
|
|
831
|
-
control.invalid ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
832
|
-
className: "tp-invalidMsg",
|
|
833
|
-
role: "status",
|
|
834
|
-
children: t("invalidNumber")
|
|
835
|
-
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
836
|
-
className: "tp-hint",
|
|
837
|
-
children: t(control.hintKey)
|
|
838
878
|
})
|
|
839
879
|
]
|
|
840
880
|
});
|
|
@@ -1060,11 +1100,13 @@ window.__ModuleLoader__.load({
|
|
|
1060
1100
|
.tp-card.tp-open{background:var(--dsw-alias-bg-layer-2);border-color:var(--dsw-alias-label-dimmed)}
|
|
1061
1101
|
.tp-header{width:100%;appearance:none;border:0;background:none;font:inherit;color:inherit;text-align:left;cursor:pointer;display:flex;align-items:center;gap:12px;padding:14px 16px;border-radius:12px}
|
|
1062
1102
|
.tp-header:active{background:var(--dsw-alias-bg-layer-2)}
|
|
1063
|
-
.tp-header:focus-visible{outline:2px solid var(--dsw-alias-
|
|
1103
|
+
.tp-header:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:-2px}
|
|
1064
1104
|
.tp-headText{flex:1;min-width:0;display:flex;flex-direction:column;gap:4px}
|
|
1065
1105
|
.tp-name{font-size:15px;font-weight:600;line-height:1.4;color:var(--dsw-alias-label-primary)}
|
|
1066
1106
|
.tp-desc{font-size:13px;line-height:1.5;color:var(--dsw-alias-label-tertiary)}
|
|
1067
|
-
.tp-pending{flex:none;border-radius:999px;padding:
|
|
1107
|
+
.tp-pending{display:inline-flex;align-items:center;gap:6px;flex:none;border-radius:999px;padding:2px 10px;font-size:11px;line-height:17px;font-weight:500;white-space:nowrap;background:var(--dsw-alias-state-business-tertiary);color:var(--dsw-alias-state-business-primary)}
|
|
1108
|
+
.tp-pending::before{content:'';flex:none;width:6px;height:6px;border-radius:999px;background:var(--dsw-alias-state-business-primary);animation:tp-pulse 1.8s ease-in-out infinite}
|
|
1109
|
+
@keyframes tp-pulse{0%,100%{opacity:.35;transform:scale(.85)}50%{opacity:1;transform:scale(1)}}
|
|
1068
1110
|
.tp-chevron{flex:none;color:var(--dsw-alias-label-tertiary);transition:transform .16s;display:inline-flex}
|
|
1069
1111
|
.tp-chevron.tp-open{transform:rotate(180deg)}
|
|
1070
1112
|
.tp-body{border-top:1px solid var(--dsw-alias-border-l2);margin:0 16px;padding-top:4px;display:grid;grid-template-rows:0fr;opacity:0;transition:grid-template-rows .22s cubic-bezier(.22,.61,.36,1),opacity .16s ease}
|
|
@@ -1082,10 +1124,12 @@ window.__ModuleLoader__.load({
|
|
|
1082
1124
|
.tp-discard{border-color:var(--dsw-alias-border-l2);background:none;color:var(--dsw-alias-label-secondary)}
|
|
1083
1125
|
.tp-discard:hover:not(:disabled){color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-label-dimmed)}
|
|
1084
1126
|
.tp-save{background:var(--dsw-alias-label-primary);color:var(--dsw-alias-bg-layer-3)}
|
|
1127
|
+
.tp-save:hover:not(:disabled){background:var(--dsw-alias-button-primary-hover)}
|
|
1085
1128
|
.tp-discard:disabled,.tp-save:disabled{opacity:.4;cursor:default}
|
|
1086
|
-
.tp-discard:focus-visible,.tp-save:focus-visible{outline:2px solid var(--dsw-alias-
|
|
1129
|
+
.tp-discard:focus-visible,.tp-save:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:1px}
|
|
1087
1130
|
@media (prefers-reduced-motion: reduce){
|
|
1088
1131
|
.tp-card,.tp-header,.tp-chevron,.tp-body,.tp-thumb,.tp-switchTrack,.tp-discard,.tp-save{transition:none}
|
|
1132
|
+
.tp-pending::before{animation:none}
|
|
1089
1133
|
}
|
|
1090
1134
|
`;
|
|
1091
1135
|
/** Inject the card stylesheet once per page; the loader removes plugin-owned style tags on unload. */
|
|
@@ -1595,6 +1639,8 @@ window.__ModuleLoader__.load({
|
|
|
1595
1639
|
*/
|
|
1596
1640
|
/** Logical RPC channel registered by the host half of this plugin. */
|
|
1597
1641
|
const TOOL_PLUS_RPC_CHANNEL = "/tool-plus";
|
|
1642
|
+
/** Endpoint that reports the rmSafe injection status (query = ensure + verify). */
|
|
1643
|
+
const RM_SAFE_STATUS_ENDPOINT = "rmSafe/status";
|
|
1598
1644
|
//#endregion
|
|
1599
1645
|
//#region src/client/web-connection-rpc.ts
|
|
1600
1646
|
/**
|
|
@@ -1983,6 +2029,27 @@ window.__ModuleLoader__.load({
|
|
|
1983
2029
|
};
|
|
1984
2030
|
}
|
|
1985
2031
|
//#endregion
|
|
2032
|
+
//#region src/client/rm-safe-status-text.ts
|
|
2033
|
+
/** Locale key of one rmSafe failure reason. */
|
|
2034
|
+
function rmSafeReasonKey(reason) {
|
|
2035
|
+
switch (reason) {
|
|
2036
|
+
case "snapshot-unavailable": return "rmSafeReasonSnapshotUnavailable";
|
|
2037
|
+
case "cli-missing": return "rmSafeReasonCliMissing";
|
|
2038
|
+
case "script-write-failed": return "rmSafeReasonScriptWriteFailed";
|
|
2039
|
+
case "snapshot-write-failed": return "rmSafeReasonSnapshotWriteFailed";
|
|
2040
|
+
case "runtime-not-effective": return "rmSafeReasonRuntimeNotEffective";
|
|
2041
|
+
}
|
|
2042
|
+
}
|
|
2043
|
+
/**
|
|
2044
|
+
* Localized text of one rmSafe/status result. `disabled` maps to the empty
|
|
2045
|
+
* string — the caller decides not to surface it.
|
|
2046
|
+
*/
|
|
2047
|
+
function rmSafeStatusText(t, value) {
|
|
2048
|
+
if (value.status === "failed") return t("rmSafeStatusFailed").replace("{reason}", t(rmSafeReasonKey(value.reason)));
|
|
2049
|
+
if (value.status === "disabled") return "";
|
|
2050
|
+
return value.runtime === "function" ? t("rmSafeStatusInjected") : t("rmSafeStatusInjectedUnverified");
|
|
2051
|
+
}
|
|
2052
|
+
//#endregion
|
|
1986
2053
|
//#region src/client/ToolPlusSection.tsx
|
|
1987
2054
|
/**
|
|
1988
2055
|
* Tool Plus settings section — the plugin's own page in the Settings panel,
|
|
@@ -2002,37 +2069,69 @@ window.__ModuleLoader__.load({
|
|
|
2002
2069
|
* Tab chrome mirrors the official Plugins settings section
|
|
2003
2070
|
* (ui-settings-plugins): `role="tablist"` + tab/panel roles, aria wiring,
|
|
2004
2071
|
* and arrow/Home/End keyboard roaming.
|
|
2072
|
+
*
|
|
2073
|
+
* The page is pinned to the host settings pane: the heading, intro and tablist
|
|
2074
|
+
* stay put, the save bar sits at the bottom, and the tab panel is the only
|
|
2075
|
+
* scroller — so the scrollbar spans just the band between them.
|
|
2005
2076
|
* @module @xiaoso/dsh-tool-plus/client
|
|
2006
2077
|
*/
|
|
2007
|
-
/**
|
|
2078
|
+
/**
|
|
2079
|
+
* Page CSS, keyed by `data-plugin-css` and injected once.
|
|
2080
|
+
*
|
|
2081
|
+
* Design language: official settings-section rhythm (18/600 heading, 13px
|
|
2082
|
+
* tertiary intro, 760px content column) on the host's `--dsw-alias-*`
|
|
2083
|
+
* tokens, so both themes adapt automatically. Tab chrome is the official
|
|
2084
|
+
* underline tablist (like the Plugins section), upgraded with a single
|
|
2085
|
+
* sliding brand-blue underline; group cards float above the modal surface
|
|
2086
|
+
* (layer-3). No backdrop-filter anywhere — solid token colors only.
|
|
2087
|
+
* All motion is transform/opacity only (GPU-composited): the underline
|
|
2088
|
+
* FLIP is driven from JS, entrance stagger and panel fades are CSS
|
|
2089
|
+
* keyframes, and the unsaved dot pulses opacity/scale. Everything shuts
|
|
2090
|
+
* off under prefers-reduced-motion (CSS here, matchMedia for the
|
|
2091
|
+
* JS-driven underline).
|
|
2092
|
+
*/
|
|
2008
2093
|
const CSS = `
|
|
2009
|
-
.tps{display:flex;flex-direction:column;gap:16px;padding:
|
|
2010
|
-
.tps-heading{margin:0;font-size:18px;font-weight:600;line-height:26px;color:var(--dsw-alias-label-primary)}
|
|
2011
|
-
.tps-intro{margin:0;font-size:13px;line-height:20px;color:var(--dsw-alias-label-tertiary)}
|
|
2012
|
-
.tps-tabs{display:flex;flex-
|
|
2013
|
-
.tps-tab{appearance:none;border:none;background:none;font:inherit;font-size:13px;line-height:20px;color:var(--dsw-alias-label-
|
|
2014
|
-
.tps-tab:hover:not([data-active="true"]){
|
|
2015
|
-
.tps-tab[data-active="true"]{
|
|
2016
|
-
.tps-tab:focus-visible{outline:2px solid var(--dsw-alias-
|
|
2017
|
-
.tps-
|
|
2018
|
-
.tps-
|
|
2019
|
-
.tps-
|
|
2020
|
-
.tps-
|
|
2094
|
+
.tps-section{box-sizing:border-box;display:flex;flex-direction:column;gap:16px;padding-top:2px}
|
|
2095
|
+
.tps-heading{margin:0;font-size:18px;font-weight:600;line-height:26px;color:var(--dsw-alias-label-primary);animation:tps-rise .38s cubic-bezier(.22,1,.36,1) both}
|
|
2096
|
+
.tps-intro{margin:0;font-size:13px;line-height:20px;color:var(--dsw-alias-label-tertiary);max-width:64ch;animation:tps-rise .38s cubic-bezier(.22,1,.36,1) .05s both}
|
|
2097
|
+
.tps-tabs{position:relative;display:flex;align-items:flex-end;gap:22px;border-bottom:1px solid var(--dsw-alias-border-l2);margin-top:2px;animation:tps-rise .38s cubic-bezier(.22,1,.36,1) .1s both}
|
|
2098
|
+
.tps-tab{position:relative;appearance:none;border:none;background:none;font:inherit;font-size:13px;line-height:20px;color:var(--dsw-alias-label-tertiary);padding:7px 1px 9px;cursor:pointer;white-space:nowrap;transition:color .16s ease}
|
|
2099
|
+
.tps-tab:hover:not([data-active="true"]){color:var(--dsw-alias-label-primary)}
|
|
2100
|
+
.tps-tab[data-active="true"]{color:var(--dsw-alias-label-primary)}
|
|
2101
|
+
.tps-tab:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:2px;border-radius:2px}
|
|
2102
|
+
.tps-tabIndicator{position:absolute;bottom:-1px;left:0;height:2px;border-radius:2px 2px 0 0;background:var(--dsw-alias-state-business-primary);pointer-events:none;transform-origin:left center;will-change:transform}
|
|
2103
|
+
.tps-panel{display:flex;flex:1 1 auto;flex-direction:column;gap:14px;min-width:0;min-height:0;overflow-y:auto;overscroll-behavior:contain;scrollbar-gutter:stable;scrollbar-width:thin;animation:tps-panel-in .24s cubic-bezier(.22,1,.36,1) both}
|
|
2104
|
+
.tps-panel::-webkit-scrollbar{width:10px}
|
|
2105
|
+
.tps-panel::-webkit-scrollbar-track{background:transparent}
|
|
2106
|
+
.tps-panel::-webkit-scrollbar-thumb{border:3px solid transparent;border-radius:999px;background-color:color-mix(in srgb, var(--dsw-alias-label-tertiary) 45%, transparent);background-clip:content-box}
|
|
2107
|
+
.tps-panel::-webkit-scrollbar-thumb:hover{background-color:color-mix(in srgb, var(--dsw-alias-label-tertiary) 75%, transparent)}
|
|
2108
|
+
.tps-group{display:flex;flex-direction:column}
|
|
2109
|
+
.tps-groupTitle{font-size:13px;font-weight:600;line-height:20px;color:var(--dsw-alias-label-primary);margin:0 0 6px;padding:0 2px}
|
|
2110
|
+
.tps-card{display:flex;flex-direction:column;border:1px solid var(--dsw-alias-border-l2);border-radius:12px;background:var(--dsw-alias-bg-layer-3);padding:0 16px}
|
|
2111
|
+
.tps-empty{border:1px dashed var(--dsw-alias-border-l3);border-radius:12px;padding:24px 16px;display:flex;flex-direction:column;align-items:center;text-align:center;gap:4px}
|
|
2021
2112
|
.tps-emptyTitle{margin:0;font-size:14px;font-weight:600;line-height:22px;color:var(--dsw-alias-label-primary)}
|
|
2022
2113
|
.tps-emptyHint{margin:0;font-size:12px;line-height:18px;color:var(--dsw-alias-label-tertiary)}
|
|
2023
|
-
.tps-readOnly{margin:0;font-size:12px;line-height:18px;color:var(--dsw-alias-label-tertiary)}
|
|
2024
|
-
.tps-footer{display:flex;align-items:center;justify-content:flex-end;gap:
|
|
2114
|
+
.tps-readOnly{margin:0;padding:8px 12px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;background:var(--dsw-alias-bg-layer-3);font-size:12px;line-height:18px;color:var(--dsw-alias-label-tertiary)}
|
|
2115
|
+
.tps-footer{flex:none;display:flex;align-items:center;justify-content:flex-end;gap:10px;padding:12px 0;border-top:1px solid var(--dsw-alias-border-l2)}
|
|
2025
2116
|
.tps-failed{flex:1;min-width:0;margin:0;font-size:12px;line-height:1.5;color:var(--dsw-alias-label-error)}
|
|
2026
2117
|
.tps-applies{flex:1;min-width:0;font-size:12px;line-height:1.5;color:var(--dsw-alias-label-tertiary)}
|
|
2027
|
-
.tps-
|
|
2118
|
+
.tps-unsaved{display:inline-flex;align-items:center;gap:6px;flex:none;border-radius:999px;padding:2px 10px;font-size:11px;line-height:17px;font-weight:500;color:var(--dsw-alias-state-business-primary);background:var(--dsw-alias-state-business-tertiary)}
|
|
2119
|
+
.tps-unsavedDot{flex:none;width:6px;height:6px;border-radius:999px;background:var(--dsw-alias-state-business-primary);animation:tps-pulse 1.8s ease-in-out infinite}
|
|
2120
|
+
.tps-discard,.tps-save{appearance:none;border:1px solid transparent;border-radius:8px;padding:5px 14px;font:inherit;font-size:13px;line-height:1.5;cursor:pointer;transition:transform .1s ease,background .16s ease,border-color .16s ease,color .16s ease}
|
|
2028
2121
|
.tps-discard:active:not(:disabled),.tps-save:active:not(:disabled){transform:scale(.97)}
|
|
2029
2122
|
.tps-discard{border-color:var(--dsw-alias-border-l2);background:none;color:var(--dsw-alias-label-secondary)}
|
|
2030
|
-
.tps-discard:hover:not(:disabled){color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-
|
|
2123
|
+
.tps-discard:hover:not(:disabled){color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-border-l4)}
|
|
2031
2124
|
.tps-save{background:var(--dsw-alias-label-primary);color:var(--dsw-alias-bg-layer-3)}
|
|
2125
|
+
.tps-save:hover:not(:disabled){background:var(--dsw-alias-button-primary-hover)}
|
|
2032
2126
|
.tps-discard:disabled,.tps-save:disabled{opacity:.4;cursor:default}
|
|
2033
|
-
.tps-discard:focus-visible,.tps-save:focus-visible{outline:2px solid var(--dsw-alias-
|
|
2127
|
+
.tps-discard:focus-visible,.tps-save:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:1px}
|
|
2128
|
+
@keyframes tps-rise{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:none}}
|
|
2129
|
+
@keyframes tps-panel-in{from{opacity:0;transform:translateY(6px)}to{opacity:1;transform:none}}
|
|
2130
|
+
@keyframes tps-pulse{0%,100%{opacity:.35;transform:scale(.85)}50%{opacity:1;transform:scale(1)}}
|
|
2034
2131
|
@media (prefers-reduced-motion: reduce){
|
|
2132
|
+
.tps-heading,.tps-intro,.tps-tabs,.tps-panel,.tps-unsavedDot{animation:none}
|
|
2035
2133
|
.tps-tab,.tps-discard,.tps-save{transition:none}
|
|
2134
|
+
.tps-tabIndicator{will-change:auto}
|
|
2036
2135
|
}
|
|
2037
2136
|
`;
|
|
2038
2137
|
/** Inject the section stylesheet once per page. */
|
|
@@ -2052,6 +2151,17 @@ window.__ModuleLoader__.load({
|
|
|
2052
2151
|
const TOOL_TABS = TOOL_PLUS_TABS;
|
|
2053
2152
|
/** All fields the page edits (one shared form). */
|
|
2054
2153
|
const ALL_FIELDS = TOOL_PLUS_FIELDS;
|
|
2154
|
+
/** Floor for the pinned page height, so an unusually short pane never collapses the panel. */
|
|
2155
|
+
const MIN_PAGE_HEIGHT = 240;
|
|
2156
|
+
/**
|
|
2157
|
+
* Nearest ancestor that scrolls vertically — the host settings pane hosting
|
|
2158
|
+
* this section. The pane is app chrome with no stable class, so it is found by
|
|
2159
|
+
* walking up from the section root.
|
|
2160
|
+
*/
|
|
2161
|
+
function scrollPaneOf(element) {
|
|
2162
|
+
for (let node = element.parentElement; node !== null; node = node.parentElement) if (/(auto|scroll|overlay)/.test(getComputedStyle(node).overflowY)) return node;
|
|
2163
|
+
return null;
|
|
2164
|
+
}
|
|
2055
2165
|
/** Current effective value of a boolean/select control (staged or resolved). */
|
|
2056
2166
|
function controlValue(form, field) {
|
|
2057
2167
|
for (const group of form.groups) {
|
|
@@ -2124,48 +2234,51 @@ window.__ModuleLoader__.load({
|
|
|
2124
2234
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", {
|
|
2125
2235
|
className: "tps-groupTitle",
|
|
2126
2236
|
children: t(TOOL_PLUS_GROUP_LABELS[group.group] ?? "title")
|
|
2127
|
-
}),
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2237
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2238
|
+
className: "tps-card",
|
|
2239
|
+
children: group.fields.map((field) => {
|
|
2240
|
+
if (field.kind === "action") {
|
|
2241
|
+
const actionControl = {
|
|
2242
|
+
field: field.name,
|
|
2243
|
+
labelKey: field.labelKey,
|
|
2244
|
+
hintKey: field.hintKey,
|
|
2245
|
+
actionKey: field.actionKey ?? ""
|
|
2246
|
+
};
|
|
2247
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ActionRow, {
|
|
2248
|
+
t,
|
|
2249
|
+
control: actionControl,
|
|
2250
|
+
disabled: !writable,
|
|
2251
|
+
running: actionState[field.name] === "running",
|
|
2252
|
+
result: actionResults[field.name] ?? null,
|
|
2253
|
+
onRun: (_actionKey) => runAction(field)
|
|
2254
|
+
}, field.name);
|
|
2255
|
+
}
|
|
2256
|
+
const entry = controlOf(field);
|
|
2257
|
+
if (entry === void 0) return null;
|
|
2258
|
+
if (entry.kind === "number") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(NumberRow, {
|
|
2136
2259
|
t,
|
|
2137
|
-
control:
|
|
2260
|
+
control: entry.control,
|
|
2138
2261
|
disabled: !writable,
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
onRun: (_actionKey) => runAction(field)
|
|
2262
|
+
onEdit: form.actions.edit,
|
|
2263
|
+
onReset: form.actions.resetField
|
|
2142
2264
|
}, field.name);
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
onReset: form.actions.resetField
|
|
2161
|
-
}, field.name);
|
|
2162
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToggleRow, {
|
|
2163
|
-
t,
|
|
2164
|
-
control: entry.control,
|
|
2165
|
-
disabled: !writable,
|
|
2166
|
-
onToggle: form.onToggle,
|
|
2167
|
-
onReset: form.actions.resetField
|
|
2168
|
-
}, field.name);
|
|
2265
|
+
if (entry.kind === "select") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SelectRow, {
|
|
2266
|
+
t,
|
|
2267
|
+
control: entry.control,
|
|
2268
|
+
disabled: !writable,
|
|
2269
|
+
open: openSelect === field.name,
|
|
2270
|
+
onOpenChange,
|
|
2271
|
+
onEdit: form.editSelect,
|
|
2272
|
+
onReset: form.actions.resetField
|
|
2273
|
+
}, field.name);
|
|
2274
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToggleRow, {
|
|
2275
|
+
t,
|
|
2276
|
+
control: entry.control,
|
|
2277
|
+
disabled: !writable,
|
|
2278
|
+
onToggle: form.onToggle,
|
|
2279
|
+
onReset: form.actions.resetField
|
|
2280
|
+
}, field.name);
|
|
2281
|
+
})
|
|
2169
2282
|
})]
|
|
2170
2283
|
}, group.group)) });
|
|
2171
2284
|
}
|
|
@@ -2173,12 +2286,34 @@ window.__ModuleLoader__.load({
|
|
|
2173
2286
|
function ToolPlusSection(props) {
|
|
2174
2287
|
const { t, scope } = props;
|
|
2175
2288
|
const form = useToolForm(scope, ALL_FIELDS);
|
|
2289
|
+
const sectionRef = (0, react.useRef)(null);
|
|
2176
2290
|
const tabsId = (0, react.useRef)(`tps-${Math.random().toString(36).slice(2, 8)}`);
|
|
2177
2291
|
const tabRefs = (0, react.useRef)([]);
|
|
2178
2292
|
const [activeId, setActiveId] = (0, react.useState)();
|
|
2179
2293
|
const [openSelect, setOpenSelect] = (0, react.useState)(null);
|
|
2180
2294
|
const [actionState, setActionState] = (0, react.useState)({});
|
|
2181
2295
|
const [actionResults, setActionResults] = (0, react.useState)({});
|
|
2296
|
+
const [rmSafeToast, setRmSafeToast] = (0, react.useState)(null);
|
|
2297
|
+
const handleSave = (0, react.useCallback)(async () => {
|
|
2298
|
+
const before = Boolean(scope.getSnapshot().value?.rmSafe ?? true);
|
|
2299
|
+
await form.actions.save();
|
|
2300
|
+
const after = Boolean(scope.getSnapshot().value?.rmSafe ?? true);
|
|
2301
|
+
if (before === after || !after) return;
|
|
2302
|
+
try {
|
|
2303
|
+
const result = await createWebConnectionRpc().call(TOOL_PLUS_RPC_CHANNEL, RM_SAFE_STATUS_ENDPOINT, {});
|
|
2304
|
+
if (!result.ok) return;
|
|
2305
|
+
const value = result.value;
|
|
2306
|
+
if (value.status === "disabled") return;
|
|
2307
|
+
setRmSafeToast({
|
|
2308
|
+
text: rmSafeStatusText(t, value),
|
|
2309
|
+
ok: value.status === "injected"
|
|
2310
|
+
});
|
|
2311
|
+
} catch {}
|
|
2312
|
+
}, [
|
|
2313
|
+
scope,
|
|
2314
|
+
form,
|
|
2315
|
+
t
|
|
2316
|
+
]);
|
|
2182
2317
|
const runAction = (0, react.useCallback)((field) => {
|
|
2183
2318
|
const actionKey = field.actionKey;
|
|
2184
2319
|
if (!actionKey) return;
|
|
@@ -2231,12 +2366,88 @@ window.__ModuleLoader__.load({
|
|
|
2231
2366
|
injectSectionCss();
|
|
2232
2367
|
injectSettingsRowsCss();
|
|
2233
2368
|
}, []);
|
|
2234
|
-
if (!form.shell.available) return null;
|
|
2235
2369
|
const active = TOOL_TABS.find((row) => row.id === activeId)?.id ?? TOOL_TABS[0]?.id;
|
|
2370
|
+
const tabsRef = (0, react.useRef)(null);
|
|
2371
|
+
const indicatorRef = (0, react.useRef)(null);
|
|
2372
|
+
const committedRect = (0, react.useRef)(null);
|
|
2373
|
+
const commitIndicator = (0, react.useCallback)((animate) => {
|
|
2374
|
+
const tabs = tabsRef.current;
|
|
2375
|
+
const indicator = indicatorRef.current;
|
|
2376
|
+
if (tabs === null || indicator === null || active === void 0) return;
|
|
2377
|
+
const tab = tabs.querySelector(`[data-tps-tab="${active}"]`);
|
|
2378
|
+
if (tab === null) return;
|
|
2379
|
+
const left = tab.offsetLeft;
|
|
2380
|
+
const width = tab.offsetWidth;
|
|
2381
|
+
const previous = committedRect.current;
|
|
2382
|
+
committedRect.current = {
|
|
2383
|
+
left,
|
|
2384
|
+
width
|
|
2385
|
+
};
|
|
2386
|
+
if (previous !== null && previous.left === left && previous.width === width) return;
|
|
2387
|
+
const reduce = typeof window !== "undefined" && typeof window.matchMedia === "function" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
2388
|
+
if (!animate || reduce || previous === null) {
|
|
2389
|
+
indicator.style.transition = "none";
|
|
2390
|
+
indicator.style.width = `${width}px`;
|
|
2391
|
+
indicator.style.transform = `translateX(${left}px)`;
|
|
2392
|
+
return;
|
|
2393
|
+
}
|
|
2394
|
+
indicator.style.transition = "none";
|
|
2395
|
+
indicator.style.width = `${width}px`;
|
|
2396
|
+
indicator.style.transform = `translateX(${previous.left}px) scaleX(${previous.width / width})`;
|
|
2397
|
+
indicator.offsetWidth;
|
|
2398
|
+
indicator.style.transition = "transform .24s cubic-bezier(.22,1,.36,1)";
|
|
2399
|
+
indicator.style.transform = `translateX(${left}px) scaleX(1)`;
|
|
2400
|
+
}, [active]);
|
|
2401
|
+
(0, react.useLayoutEffect)(() => {
|
|
2402
|
+
commitIndicator(true);
|
|
2403
|
+
}, [commitIndicator]);
|
|
2404
|
+
const commitRef = (0, react.useRef)(commitIndicator);
|
|
2405
|
+
commitRef.current = commitIndicator;
|
|
2406
|
+
(0, react.useEffect)(() => {
|
|
2407
|
+
const tabs = tabsRef.current;
|
|
2408
|
+
if (tabs === null || typeof ResizeObserver === "undefined") return;
|
|
2409
|
+
const observer = new ResizeObserver(() => commitRef.current(false));
|
|
2410
|
+
observer.observe(tabs);
|
|
2411
|
+
for (const tab of Array.from(tabs.querySelectorAll("[data-tps-tab]"))) observer.observe(tab);
|
|
2412
|
+
return () => {
|
|
2413
|
+
observer.disconnect();
|
|
2414
|
+
};
|
|
2415
|
+
}, []);
|
|
2416
|
+
(0, react.useLayoutEffect)(() => {
|
|
2417
|
+
const root = sectionRef.current;
|
|
2418
|
+
if (root === null || typeof ResizeObserver === "undefined") return;
|
|
2419
|
+
const pane = scrollPaneOf(root);
|
|
2420
|
+
if (pane === null) return;
|
|
2421
|
+
let frame = 0;
|
|
2422
|
+
const measure = () => {
|
|
2423
|
+
frame = 0;
|
|
2424
|
+
const style = getComputedStyle(pane);
|
|
2425
|
+
const paddingTop = parseFloat(style.paddingTop) || 0;
|
|
2426
|
+
const contentHeight = pane.clientHeight - paddingTop - (parseFloat(style.paddingBottom) || 0);
|
|
2427
|
+
const offset = root.getBoundingClientRect().top - pane.getBoundingClientRect().top - (parseFloat(style.borderTopWidth) || 0) - paddingTop + pane.scrollTop;
|
|
2428
|
+
const next = `${Math.max(MIN_PAGE_HEIGHT, Math.round(contentHeight - offset))}px`;
|
|
2429
|
+
if (root.style.height !== next) root.style.height = next;
|
|
2430
|
+
};
|
|
2431
|
+
const schedule = () => {
|
|
2432
|
+
if (frame === 0) frame = requestAnimationFrame(measure);
|
|
2433
|
+
};
|
|
2434
|
+
measure();
|
|
2435
|
+
const observer = new ResizeObserver(schedule);
|
|
2436
|
+
observer.observe(pane);
|
|
2437
|
+
window.addEventListener("resize", schedule);
|
|
2438
|
+
return () => {
|
|
2439
|
+
if (frame !== 0) cancelAnimationFrame(frame);
|
|
2440
|
+
observer.disconnect();
|
|
2441
|
+
window.removeEventListener("resize", schedule);
|
|
2442
|
+
root.style.height = "";
|
|
2443
|
+
};
|
|
2444
|
+
}, [form.shell.available]);
|
|
2445
|
+
if (!form.shell.available) return null;
|
|
2236
2446
|
const selectTab = (id) => setActiveId(id);
|
|
2237
2447
|
const blocked = !form.shell.dirty || form.shell.invalid || form.shell.saving;
|
|
2238
2448
|
const writable = form.shell.writable;
|
|
2239
2449
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2450
|
+
ref: sectionRef,
|
|
2240
2451
|
className: "tps-section",
|
|
2241
2452
|
children: [
|
|
2242
2453
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
|
|
@@ -2247,11 +2458,16 @@ window.__ModuleLoader__.load({
|
|
|
2247
2458
|
className: "tps-intro",
|
|
2248
2459
|
children: t("pageDescription")
|
|
2249
2460
|
}),
|
|
2250
|
-
/* @__PURE__ */ (0, react_jsx_runtime.
|
|
2461
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2462
|
+
ref: tabsRef,
|
|
2251
2463
|
className: "tps-tabs",
|
|
2252
2464
|
role: "tablist",
|
|
2253
2465
|
"aria-label": t("pageTitle"),
|
|
2254
|
-
children:
|
|
2466
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2467
|
+
ref: indicatorRef,
|
|
2468
|
+
className: "tps-tabIndicator",
|
|
2469
|
+
"aria-hidden": "true"
|
|
2470
|
+
}), TOOL_TABS.map((row, index) => {
|
|
2255
2471
|
const selected = row.id === active;
|
|
2256
2472
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2257
2473
|
ref: (element) => {
|
|
@@ -2264,6 +2480,7 @@ window.__ModuleLoader__.load({
|
|
|
2264
2480
|
"aria-selected": selected,
|
|
2265
2481
|
"aria-controls": `${tabsId.current}-panel-${row.id}`,
|
|
2266
2482
|
"data-active": selected ? "true" : void 0,
|
|
2483
|
+
"data-tps-tab": row.id,
|
|
2267
2484
|
tabIndex: selected ? 0 : -1,
|
|
2268
2485
|
onClick: () => selectTab(row.id),
|
|
2269
2486
|
onKeyDown: (event) => {
|
|
@@ -2291,7 +2508,7 @@ window.__ModuleLoader__.load({
|
|
|
2291
2508
|
},
|
|
2292
2509
|
children: t(row.labelKey)
|
|
2293
2510
|
}, row.id);
|
|
2294
|
-
})
|
|
2511
|
+
})]
|
|
2295
2512
|
}),
|
|
2296
2513
|
TOOL_TABS.filter((row) => row.id === active).map((row) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2297
2514
|
id: `${tabsId.current}-panel-${row.id}`,
|
|
@@ -2326,6 +2543,14 @@ window.__ModuleLoader__.load({
|
|
|
2326
2543
|
className: "tps-applies",
|
|
2327
2544
|
children: t("appliesTo")
|
|
2328
2545
|
}) : null,
|
|
2546
|
+
!form.shell.failed && form.shell.dirty ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
2547
|
+
className: "tps-unsaved",
|
|
2548
|
+
role: "status",
|
|
2549
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2550
|
+
className: "tps-unsavedDot",
|
|
2551
|
+
"aria-hidden": "true"
|
|
2552
|
+
}), t("unsaved")]
|
|
2553
|
+
}) : null,
|
|
2329
2554
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2330
2555
|
type: "button",
|
|
2331
2556
|
className: "tps-discard",
|
|
@@ -2337,10 +2562,17 @@ window.__ModuleLoader__.load({
|
|
|
2337
2562
|
type: "button",
|
|
2338
2563
|
className: "tps-save",
|
|
2339
2564
|
disabled: blocked,
|
|
2340
|
-
onClick: () => void
|
|
2565
|
+
onClick: () => void handleSave(),
|
|
2341
2566
|
children: form.shell.saving ? t("saving") : t("save")
|
|
2342
2567
|
})
|
|
2343
2568
|
]
|
|
2569
|
+
}),
|
|
2570
|
+
rmSafeToast !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Toast, {
|
|
2571
|
+
text: rmSafeToast.text,
|
|
2572
|
+
icon: rmSafeToast.ok ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCheckOutline16, {}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconWarningOutline16, {}),
|
|
2573
|
+
onDone: () => {
|
|
2574
|
+
setRmSafeToast(null);
|
|
2575
|
+
}
|
|
2344
2576
|
})
|
|
2345
2577
|
]
|
|
2346
2578
|
});
|
|
@@ -2480,6 +2712,16 @@ window.__ModuleLoader__.load({
|
|
|
2480
2712
|
nonInteractiveEnvHint: "对非交互使用加固环境:关闭分页器与彩色输出,不弹交互提示。",
|
|
2481
2713
|
snapshotEnabled: "Shell 快照",
|
|
2482
2714
|
snapshotEnabledHint: "把用户的 rc 文件(别名、函数、选项)快照进会话 shell。",
|
|
2715
|
+
rmSafe: "安全 rm",
|
|
2716
|
+
rmSafeHint: "在会话 shell 里重定义 rm:删除的文件移入系统回收站而非永久删除;command rm 仍走真删除。",
|
|
2717
|
+
rmSafeStatusInjected: "安全 rm 已注入并验证:rm 进入回收站",
|
|
2718
|
+
rmSafeStatusInjectedUnverified: "安全 rm 已注入(运行时验证不可用)",
|
|
2719
|
+
rmSafeStatusFailed: "安全 rm 注入失败:{reason},rm 将永久删除",
|
|
2720
|
+
rmSafeReasonSnapshotUnavailable: "快照不可用",
|
|
2721
|
+
rmSafeReasonCliMissing: "trash-cli 产物缺失",
|
|
2722
|
+
rmSafeReasonScriptWriteFailed: "脚本写入失败",
|
|
2723
|
+
rmSafeReasonSnapshotWriteFailed: "快照不可写",
|
|
2724
|
+
rmSafeReasonRuntimeNotEffective: "运行时未生效",
|
|
2483
2725
|
useShellCommandWrapper: "用 bash -c 包裹命令",
|
|
2484
2726
|
useShellCommandWrapperHint: "把每条命令用 bash -c 包裹,以获得完整的 bash 环境。",
|
|
2485
2727
|
outputTruncateStrategy: "截断按",
|
|
@@ -2651,6 +2893,16 @@ window.__ModuleLoader__.load({
|
|
|
2651
2893
|
nonInteractiveEnvHint: "Harden the environment for non-interactive use: no pagers, no prompts, no color.",
|
|
2652
2894
|
snapshotEnabled: "Shell snapshot",
|
|
2653
2895
|
snapshotEnabledHint: "Capture the user's rc file (aliases, functions, options) into the session shell.",
|
|
2896
|
+
rmSafe: "Safe rm",
|
|
2897
|
+
rmSafeHint: "Redefine rm in the session shell: deleted files move to the system trash instead of being permanently removed; `command rm` still deletes for real.",
|
|
2898
|
+
rmSafeStatusInjected: "Safe rm injected and verified: rm moves to trash",
|
|
2899
|
+
rmSafeStatusInjectedUnverified: "Safe rm injected (runtime verification unavailable)",
|
|
2900
|
+
rmSafeStatusFailed: "Safe rm injection failed: {reason}, rm deletes permanently",
|
|
2901
|
+
rmSafeReasonSnapshotUnavailable: "snapshot unavailable",
|
|
2902
|
+
rmSafeReasonCliMissing: "trash-cli binary missing",
|
|
2903
|
+
rmSafeReasonScriptWriteFailed: "script write failed",
|
|
2904
|
+
rmSafeReasonSnapshotWriteFailed: "snapshot not writable",
|
|
2905
|
+
rmSafeReasonRuntimeNotEffective: "not effective at runtime",
|
|
2654
2906
|
useShellCommandWrapper: "Wrap commands in bash -c",
|
|
2655
2907
|
useShellCommandWrapperHint: "Wrap every command in bash -c for a full bash environment.",
|
|
2656
2908
|
outputTruncateStrategy: "Truncate by",
|