@jayyuen66/dsh-quality-gate 0.1.3 → 0.1.5
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 +40 -0
- package/LICENSE +1 -1
- package/README.md +15 -14
- package/README.zh-CN.md +14 -13
- package/THIRD_PARTY_NOTICES.md +4 -1
- package/client.js +602 -270
- package/cordis.patch.yml +4 -2
- package/docs/DETAIL.md +24 -0
- package/docs/DETAIL.zh.md +24 -0
- package/host.js +353 -84
- package/locale/en.json +7 -0
- package/locale/zh.json +7 -0
- package/package.json +52 -34
package/client.js
CHANGED
|
@@ -7,209 +7,562 @@ window.__ModuleLoader__.load({
|
|
|
7
7
|
let exports = module.exports;
|
|
8
8
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
9
9
|
let react = require("react");
|
|
10
|
+
let zod = require("zod");
|
|
10
11
|
// node_modules/@jayyuen66/dsh-plugin-shared/dist/card-diff.js
|
|
11
|
-
function e(t, n, r) {
|
|
12
|
+
function e$1(t, n, r) {
|
|
12
13
|
if (t == null || n == null) return (t ?? null) === (n ?? null);
|
|
13
14
|
if (Object.is(t, n)) return !0;
|
|
14
15
|
if (typeof t != `object` || typeof n != `object`) return !1;
|
|
15
16
|
if (r.get(t) === n) return !0;
|
|
16
|
-
if (r.set(t, n), Array.isArray(t) || Array.isArray(n)) return !Array.isArray(t) || !Array.isArray(n) || t.length !== n.length ? !1 : t.every((t, i) => e(t, n[i], r));
|
|
17
|
+
if (r.set(t, n), Array.isArray(t) || Array.isArray(n)) return !Array.isArray(t) || !Array.isArray(n) || t.length !== n.length ? !1 : t.every((t, i) => e$1(t, n[i], r));
|
|
17
18
|
let i = Object.keys(t), a = Object.keys(n);
|
|
18
|
-
return i.length === a.length && i.every((i) => Object.hasOwn(n, i) && e(Reflect.get(t, i), Reflect.get(n, i), r));
|
|
19
|
+
return i.length === a.length && i.every((i) => Object.hasOwn(n, i) && e$1(Reflect.get(t, i), Reflect.get(n, i), r));
|
|
19
20
|
}
|
|
20
|
-
function t(t, n) {
|
|
21
|
+
function t$1(t, n) {
|
|
21
22
|
let r = [];
|
|
22
|
-
for (let i of Object.keys(t)) e(t[i], n[i], /* @__PURE__ */ new WeakMap()) || r.push(i);
|
|
23
|
+
for (let i of Object.keys(t)) e$1(t[i], n[i], /* @__PURE__ */ new WeakMap()) || r.push(i);
|
|
23
24
|
return r;
|
|
24
25
|
}
|
|
26
|
+
// node_modules/@jayyuen66/dsh-plugin-shared/dist/card-ui.js
|
|
27
|
+
const c = `
|
|
28
|
+
.dsu-form{display:flex;flex-direction:column}
|
|
29
|
+
.dsu-section{margin-top:24px;padding-top:24px;border-top:.5px solid var(--dsw-alias-border-l1)}
|
|
30
|
+
.dsu-form>.dsu-section:first-child{margin-top:0;padding-top:0;border-top:0}
|
|
31
|
+
.dsu-sectionTitle{margin:0;font-size:15px;font-weight:600;line-height:1.4;color:var(--dsw-alias-label-primary)}
|
|
32
|
+
.dsu-sectionHint{margin:4px 0 0;font-size:12px;line-height:1.5;color:var(--dsw-alias-label-tertiary)}
|
|
33
|
+
.dsu-statusbar{display:flex;flex-wrap:wrap;gap:4px 8px;margin-top:8px;font-size:12px;line-height:1.5;color:var(--dsw-alias-label-tertiary)}
|
|
34
|
+
.dsu-field{display:grid;grid-template-columns:minmax(0,1fr) minmax(240px,360px);column-gap:32px;align-items:center;padding:12px 0;border-top:.5px solid var(--dsw-alias-border-l2)}
|
|
35
|
+
.dsu-fieldMeta{display:flex;flex-direction:column;gap:4px;min-width:0}
|
|
36
|
+
.dsu-labelRow{display:flex;align-items:center;gap:6px;min-width:0}
|
|
37
|
+
.dsu-label{font-size:13px;font-weight:500;line-height:1.5;color:var(--dsw-alias-label-primary)}
|
|
38
|
+
.dsu-labelMono{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:12px}
|
|
39
|
+
.dsu-hint{margin:0;font-size:12px;line-height:1.5;color:var(--dsw-alias-label-tertiary)}
|
|
40
|
+
.dsu-control{min-width:0;display:flex;align-items:center;gap:8px}
|
|
41
|
+
.dsu-fieldWide{grid-template-columns:minmax(0,1fr);align-items:start}
|
|
42
|
+
.dsu-fieldWide .dsu-control{width:100%;max-width:720px;margin-top:2px}
|
|
43
|
+
.dsu-help{display:inline-flex;align-items:center;gap:6px;min-width:0}
|
|
44
|
+
.dsu-helpBtn{appearance:none;border:0;background:transparent;padding:0;width:16px;height:16px;display:inline-flex;align-items:center;justify-content:center;color:var(--dsw-alias-label-tertiary);cursor:help;border-radius:50%;flex:none}
|
|
45
|
+
.dsu-help:hover .dsu-helpBtn,.dsu-helpBtn:focus-visible{color:var(--dsw-alias-label-primary)}
|
|
46
|
+
.dsu-helpBtn:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:1px}
|
|
47
|
+
.dsu-tip{position:fixed;z-index:60;box-sizing:border-box;max-width:min(340px,calc(100vw - 16px));max-height:calc(100vh - 16px);overflow:auto;overflow-wrap:anywhere;padding:10px 12px;border-radius:10px;background:var(--dsw-alias-bg-layer-2);border:.5px solid var(--dsw-alias-border-l3);box-shadow:0 12px 32px rgb(0 0 0 / 45%);font-size:12px;line-height:1.6;font-weight:400;color:var(--dsw-alias-label-secondary)}
|
|
48
|
+
.dsu-input{width:100%;height:34px;padding:0 12px;border:.5px solid var(--dsw-alias-border-l4);border-radius:var(--dsw-radius-md);background:var(--dsw-alias-bg-layer-3);color:var(--dsw-alias-label-primary);font:inherit;font-size:13px}
|
|
49
|
+
.dsu-input:focus-visible{outline:none;border-color:var(--dsw-alias-state-business-primary)}
|
|
50
|
+
.dsu-input[aria-invalid="true"]{border-color:var(--dsw-alias-state-error-primary)}
|
|
51
|
+
.dsu-input:disabled{color:var(--dsw-alias-label-tertiary);cursor:default}
|
|
52
|
+
.dsu-input::placeholder{color:var(--dsw-alias-label-tertiary)}
|
|
53
|
+
.dsu-textarea{height:auto;min-height:96px;padding:8px 12px;line-height:1.6;resize:vertical}
|
|
54
|
+
.dsu-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}
|
|
55
|
+
.dsu-numberWrap{display:flex;flex-direction:column;gap:4px;width:100%;min-width:0}
|
|
56
|
+
.dsu-number{display:flex;align-items:center;gap:8px;min-width:0}
|
|
57
|
+
.dsu-number .dsu-input{min-width:0}
|
|
58
|
+
.dsu-unit{flex:none;font-size:12px;color:var(--dsw-alias-label-tertiary)}
|
|
59
|
+
.dsu-error{margin:0;font-size:12px;line-height:1.5;color:var(--dsw-alias-state-error-primary)}
|
|
60
|
+
.dsu-switch{appearance:none;width:36px;height:20px;border-radius:999px;border:0;padding:2px;background:var(--dsw-alias-border-l3);display:flex;align-items:center;cursor:pointer;flex:none;transition:background-color .16s}
|
|
61
|
+
.dsu-knob{width:16px;height:16px;border-radius:50%;background:var(--dsw-alias-label-primary);transition:transform .16s,background-color .16s}
|
|
62
|
+
.dsu-switch[aria-checked="true"]{background:var(--dsw-alias-label-primary)}
|
|
63
|
+
.dsu-switch[aria-checked="true"] .dsu-knob{transform:translateX(16px);background:var(--dsw-alias-label-primary-foreground)}
|
|
64
|
+
.dsu-switch:disabled{opacity:.4;cursor:not-allowed}
|
|
65
|
+
.dsu-switch:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:2px}
|
|
66
|
+
.dsu-selectWrap{position:relative;width:100%;min-width:0}
|
|
67
|
+
.dsu-select{display:flex;align-items:center;justify-content:space-between;gap:8px;text-align:left;cursor:pointer}
|
|
68
|
+
.dsu-select[aria-expanded="true"]{border-color:var(--dsw-alias-state-business-primary)}
|
|
69
|
+
.dsu-selectValue{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
70
|
+
.dsu-chevron{flex:none;color:var(--dsw-alias-label-tertiary);transition:transform .16s}
|
|
71
|
+
.dsu-select[aria-expanded="true"] .dsu-chevron{transform:rotate(180deg)}
|
|
72
|
+
.dsu-menu{position:fixed;z-index:70;padding:4px;background:var(--dsw-alias-bg-layer-2);border:.5px solid var(--dsw-alias-border-l3);border-radius:12px;box-shadow:0 12px 32px rgb(0 0 0 / 50%);max-height:280px;overflow:auto}
|
|
73
|
+
.dsu-option{appearance:none;border:0;background:transparent;color:var(--dsw-alias-label-primary);font:inherit;font-size:13px;line-height:1.4;width:100%;display:flex;align-items:center;justify-content:space-between;gap:12px;padding:8px 10px;border-radius:8px;cursor:pointer;text-align:left}
|
|
74
|
+
.dsu-option:hover,.dsu-option[data-active="true"]{background:var(--dsw-alias-bg-layer-3)}
|
|
75
|
+
.dsu-option:disabled{color:var(--dsw-alias-label-tertiary);cursor:not-allowed}
|
|
76
|
+
.dsu-option:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:-2px}
|
|
77
|
+
.dsu-optionGroup{padding:6px 10px 2px;font-size:11px;font-weight:600;color:var(--dsw-alias-label-tertiary)}
|
|
78
|
+
.dsu-check{opacity:0;flex:none}
|
|
79
|
+
.dsu-option[aria-selected="true"] .dsu-check{opacity:1}
|
|
80
|
+
.dsu-savebar{display:flex;align-items:center;gap:8px;padding:12px 0;margin-top:24px;border-top:.5px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-base)}
|
|
81
|
+
.dsu-savebar[data-dirty="true"]{position:sticky;bottom:0;z-index:2;box-shadow:0 -12px 24px rgb(0 0 0 / 35%)}
|
|
82
|
+
.dsu-btn{appearance:none;border:.5px solid var(--dsw-alias-border-l2);background:transparent;color:var(--dsw-alias-label-primary);font:inherit;font-size:13px;line-height:1.5;border-radius:var(--dsw-radius-md);padding:5px 14px;cursor:pointer;transition:background-color .12s,border-color .12s,color .12s,filter .12s}
|
|
83
|
+
.dsu-btn:hover:not(:disabled){background:var(--dsw-alias-bg-layer-2);border-color:var(--dsw-alias-border-l3)}
|
|
84
|
+
.dsu-btn:active:not(:disabled){background:var(--dsw-alias-bg-layer-1)}
|
|
85
|
+
.dsu-btn:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:1px}
|
|
86
|
+
.dsu-btn:disabled{opacity:.4;cursor:not-allowed}
|
|
87
|
+
.dsu-btnPrimary{background:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-label-primary);color:var(--dsw-alias-bg-layer-3)}
|
|
88
|
+
.dsu-btnPrimary:hover:not(:disabled){background:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-label-primary);filter:brightness(.9)}
|
|
89
|
+
.dsu-btnPrimary:active:not(:disabled){background:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-label-primary);filter:brightness(.84)}
|
|
90
|
+
.dsu-btnDanger{color:var(--dsw-alias-state-error-primary)}
|
|
91
|
+
.dsu-btnDanger:hover:not(:disabled){color:var(--dsw-alias-state-error-primary);border-color:var(--dsw-alias-state-error-primary);background:var(--dsw-alias-bg-layer-2)}
|
|
92
|
+
.dsu-status{margin-left:auto;font-size:12px;color:var(--dsw-alias-label-tertiary)}
|
|
93
|
+
.dsu-statusError{color:var(--dsw-alias-state-error-primary)}
|
|
94
|
+
.dsu-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(200px,100%),1fr));gap:0 24px}
|
|
95
|
+
.dsu-grid>.dsu-field{display:flex;flex-direction:column;align-items:stretch;gap:6px;padding:10px 0;border-top:.5px solid var(--dsw-alias-border-l1)}
|
|
96
|
+
.dsu-grid>.dsu-field .dsu-control{width:100%}
|
|
97
|
+
@media (max-width:640px){
|
|
98
|
+
.dsu-field{grid-template-columns:minmax(0,1fr);align-items:start}
|
|
99
|
+
.dsu-control{width:100%;max-width:720px}
|
|
100
|
+
.dsu-section{margin-top:20px;padding-top:20px}
|
|
101
|
+
}
|
|
102
|
+
@media (prefers-reduced-motion:reduce){
|
|
103
|
+
.dsu-switch,.dsu-knob,.dsu-chevron,.dsu-btn{transition:none}
|
|
104
|
+
}
|
|
105
|
+
`;
|
|
106
|
+
function l(e, t = 30) {
|
|
107
|
+
return e.length <= t ? { hint: e } : { detail: e };
|
|
108
|
+
}
|
|
109
|
+
function u(t) {
|
|
110
|
+
return (0, react.createElement)(`div`, { className: `dsu-form` }, t.children);
|
|
111
|
+
}
|
|
112
|
+
function d(t) {
|
|
113
|
+
return (0, react.createElement)(`section`, { className: `dsu-section` }, (0, react.createElement)(`h2`, { className: `dsu-sectionTitle` }, t.title), t.hint === void 0 ? null : (0, react.createElement)(`p`, { className: `dsu-sectionHint` }, t.hint), t.children);
|
|
114
|
+
}
|
|
115
|
+
function m(a) {
|
|
116
|
+
let c = (0, react.useId)(), [l, u] = (0, react.useState)(!1), [d, f] = (0, react.useState)(!1), p = (0, react.useRef)(!1), m = (0, react.useRef)(!1), h = (0, react.useRef)(!1), g = (0, react.useRef)(null), _ = (0, react.useRef)(null), v = (0, react.useRef)(null), y = (0, react.useCallback)(() => {
|
|
117
|
+
v.current !== null && (clearTimeout(v.current), v.current = null);
|
|
118
|
+
}, []), b = (0, react.useCallback)(() => {
|
|
119
|
+
let e = g.current, t = _.current;
|
|
120
|
+
/* v8 ignore next 3 -- place 只在 open 的 layout effect 与挂载期监听器里调用;refs 为空只可能是卸载竞态 */
|
|
121
|
+
if (e === null || t === null) return;
|
|
122
|
+
let n = e.getBoundingClientRect(), r = t.offsetWidth, i = t.offsetHeight, { left: a } = n, o = n.bottom + 8;
|
|
123
|
+
o + i > window.innerHeight - 8 && (o = n.top - i - 8), a + r > window.innerWidth - 8 && (a = window.innerWidth - r - 8);
|
|
124
|
+
let s = Math.max(8, window.innerWidth - r - 8), c = Math.max(8, window.innerHeight - i - 8);
|
|
125
|
+
t.style.left = `${Math.min(Math.max(8, a), s)}px`, t.style.top = `${Math.min(Math.max(8, o), c)}px`;
|
|
126
|
+
}, []), x = (0, react.useCallback)(() => {
|
|
127
|
+
y(), h.current = !1, f(!1), u(!1);
|
|
128
|
+
}, [y]), S = (0, react.useCallback)(() => {
|
|
129
|
+
y(), u(!0);
|
|
130
|
+
}, [y]), C = (0, react.useCallback)(() => {
|
|
131
|
+
y(), !(p.current || m.current || h.current) && (v.current = setTimeout(() => {
|
|
132
|
+
/* v8 ignore next 3 -- 任何重新进入/钉住都会 cancelHide;这次复查只防未来的异步路径 */
|
|
133
|
+
v.current = null, p.current || m.current || h.current || u(!1);
|
|
134
|
+
}, 120));
|
|
135
|
+
}, [y]), w = (0, react.useCallback)((e) => {
|
|
136
|
+
h.current = e, f(e);
|
|
137
|
+
}, []);
|
|
138
|
+
(0, react.useLayoutEffect)(() => {
|
|
139
|
+
if (!l) return () => {};
|
|
140
|
+
b();
|
|
141
|
+
let e = (e) => {
|
|
142
|
+
let { target: t } = e;
|
|
143
|
+
t instanceof Node && _.current?.contains(t) === !0 || (h.current ? b() : x());
|
|
144
|
+
}, t = () => {
|
|
145
|
+
b();
|
|
146
|
+
};
|
|
147
|
+
return window.addEventListener(`scroll`, e, !0), window.addEventListener(`resize`, t), () => {
|
|
148
|
+
window.removeEventListener(`scroll`, e, !0), window.removeEventListener(`resize`, t);
|
|
149
|
+
};
|
|
150
|
+
}, [
|
|
151
|
+
l,
|
|
152
|
+
a.detail,
|
|
153
|
+
b,
|
|
154
|
+
x
|
|
155
|
+
]), (0, react.useEffect)(() => {
|
|
156
|
+
if (!l) return () => {};
|
|
157
|
+
let e = (e) => {
|
|
158
|
+
e.key === `Escape` && x();
|
|
159
|
+
};
|
|
160
|
+
return document.addEventListener(`keydown`, e), () => {
|
|
161
|
+
document.removeEventListener(`keydown`, e);
|
|
162
|
+
};
|
|
163
|
+
}, [l, x]), (0, react.useEffect)(() => y, [y]);
|
|
164
|
+
let T = (0, react.createElement)(`button`, {
|
|
165
|
+
ref: g,
|
|
166
|
+
type: `button`,
|
|
167
|
+
className: `dsu-helpBtn`,
|
|
168
|
+
"aria-label": `${a.label} 说明`,
|
|
169
|
+
"aria-describedby": c,
|
|
170
|
+
"aria-pressed": d,
|
|
171
|
+
onClick: () => {
|
|
172
|
+
h.current ? x() : (w(!0), S());
|
|
173
|
+
}
|
|
174
|
+
}, (0, react.createElement)(`svg`, {
|
|
175
|
+
width: 12,
|
|
176
|
+
height: 12,
|
|
177
|
+
viewBox: `0 0 14 14`,
|
|
178
|
+
"aria-hidden": !0
|
|
179
|
+
}, (0, react.createElement)(`circle`, {
|
|
180
|
+
cx: 7,
|
|
181
|
+
cy: 7,
|
|
182
|
+
r: 6,
|
|
183
|
+
fill: `none`,
|
|
184
|
+
stroke: `currentColor`,
|
|
185
|
+
strokeWidth: 1.2
|
|
186
|
+
}), (0, react.createElement)(`path`, {
|
|
187
|
+
d: `M7 6.2v4`,
|
|
188
|
+
stroke: `currentColor`,
|
|
189
|
+
strokeWidth: 1.3,
|
|
190
|
+
strokeLinecap: `round`
|
|
191
|
+
}), (0, react.createElement)(`circle`, {
|
|
192
|
+
cx: 7,
|
|
193
|
+
cy: 4.3,
|
|
194
|
+
r: .8,
|
|
195
|
+
fill: `currentColor`
|
|
196
|
+
}))), E = (0, react.createElement)(`div`, {
|
|
197
|
+
ref: _,
|
|
198
|
+
id: c,
|
|
199
|
+
role: `tooltip`,
|
|
200
|
+
className: `dsu-tip`,
|
|
201
|
+
hidden: !l
|
|
202
|
+
}, a.detail), D = a.showLabel === !1 ? null : (0, react.createElement)(`span`, { className: `dsu-label${a.compact === !0 ? ` dsu-labelMono` : ``}` }, a.label);
|
|
203
|
+
return (0, react.createElement)(`span`, {
|
|
204
|
+
className: `dsu-help`,
|
|
205
|
+
onMouseEnter: () => {
|
|
206
|
+
p.current = !0, S();
|
|
207
|
+
},
|
|
208
|
+
onMouseLeave: () => {
|
|
209
|
+
p.current = !1, C();
|
|
210
|
+
},
|
|
211
|
+
onFocus: () => {
|
|
212
|
+
m.current = !0, S();
|
|
213
|
+
},
|
|
214
|
+
onBlur: (e) => {
|
|
215
|
+
let t = e.relatedTarget;
|
|
216
|
+
t instanceof Node && e.currentTarget.contains(t) || (m.current = !1, C());
|
|
217
|
+
}
|
|
218
|
+
}, D, T, E);
|
|
219
|
+
}
|
|
220
|
+
function h(t) {
|
|
221
|
+
let n = `dsu-label${t.compact === !0 ? ` dsu-labelMono` : ``}`, r = t.controlId === void 0 ? (0, react.createElement)(`span`, { className: n }, t.label) : (0, react.createElement)(`label`, {
|
|
222
|
+
className: n,
|
|
223
|
+
htmlFor: t.controlId
|
|
224
|
+
}, t.label);
|
|
225
|
+
return (0, react.createElement)(`div`, { className: `dsu-labelRow` }, r, t.detail === void 0 ? null : (0, react.createElement)(m, {
|
|
226
|
+
label: t.label,
|
|
227
|
+
detail: t.detail,
|
|
228
|
+
showLabel: !1
|
|
229
|
+
}));
|
|
230
|
+
}
|
|
231
|
+
function g(t) {
|
|
232
|
+
let n = t.controlId === void 0 ? void 0 : `${t.controlId}-hint`;
|
|
233
|
+
return (0, react.createElement)(`div`, { className: `dsu-field${t.wide === !0 ? ` dsu-fieldWide` : ``}` }, (0, react.createElement)(`div`, { className: `dsu-fieldMeta` }, (0, react.createElement)(h, {
|
|
234
|
+
label: t.label,
|
|
235
|
+
detail: t.detail,
|
|
236
|
+
compact: t.compact,
|
|
237
|
+
controlId: t.controlId
|
|
238
|
+
}), t.hint === void 0 ? null : (0, react.createElement)(`p`, {
|
|
239
|
+
className: `dsu-hint`,
|
|
240
|
+
id: n
|
|
241
|
+
}, t.hint)), (0, react.createElement)(`div`, { className: `dsu-control` }, t.children));
|
|
242
|
+
}
|
|
243
|
+
function _(t) {
|
|
244
|
+
let n = (0, react.useId)(), i = t.hint === void 0 ? void 0 : `${n}-hint`;
|
|
245
|
+
return (0, react.createElement)(g, {
|
|
246
|
+
...t,
|
|
247
|
+
controlId: n
|
|
248
|
+
}, (0, react.createElement)(`button`, {
|
|
249
|
+
type: `button`,
|
|
250
|
+
role: `switch`,
|
|
251
|
+
id: n,
|
|
252
|
+
className: `dsu-switch`,
|
|
253
|
+
"aria-checked": t.checked,
|
|
254
|
+
"aria-describedby": i,
|
|
255
|
+
"data-field": t.field,
|
|
256
|
+
disabled: t.disabled === !0,
|
|
257
|
+
onClick: () => {
|
|
258
|
+
t.onChange(!t.checked);
|
|
259
|
+
}
|
|
260
|
+
}, (0, react.createElement)(`span`, { className: `dsu-knob` })));
|
|
261
|
+
}
|
|
262
|
+
function v(t) {
|
|
263
|
+
let i = typeof t.value == `number` && Number.isFinite(t.value) ? String(t.value) : ``, [a, c] = (0, react.useState)(i), [l, u] = (0, react.useState)(!1), d = (0, react.useRef)(void 0), f = (0, react.useId)(), p = (0, react.useId)(), m = [t.hint === void 0 ? void 0 : `${f}-hint`, l ? p : void 0].filter((e) => e !== void 0), h = m.length === 0 ? void 0 : m.join(` `);
|
|
264
|
+
(0, react.useEffect)(() => {
|
|
265
|
+
t.value !== d.current && (d.current = t.value, c(i), u(!1));
|
|
266
|
+
}, [t.value, i]);
|
|
267
|
+
let _ = (e) => {
|
|
268
|
+
let n = t.min ?? -1 / 0, r = t.max ?? 1 / 0;
|
|
269
|
+
if (!Number.isFinite(e) || e < n || e > r) return !1;
|
|
270
|
+
let { step: i } = t;
|
|
271
|
+
if (i === void 0 || i <= 0) return !0;
|
|
272
|
+
let a = (e - (t.min ?? 0)) / i;
|
|
273
|
+
return Math.abs(a - Math.round(a)) < 1e-9;
|
|
274
|
+
}, v = (e) => {
|
|
275
|
+
if (c(e), e === ``) {
|
|
276
|
+
u(!1), d.current = void 0, t.onChange(void 0);
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
let n = Number(e);
|
|
280
|
+
_(n) ? (u(!1), d.current = n, t.onChange(n)) : u(!0);
|
|
281
|
+
}, y = () => {
|
|
282
|
+
a === `` || l || c(String(Number(a)));
|
|
283
|
+
};
|
|
284
|
+
return (0, react.createElement)(g, {
|
|
285
|
+
...t,
|
|
286
|
+
controlId: f
|
|
287
|
+
}, (0, react.createElement)(`div`, { className: `dsu-numberWrap` }, (0, react.createElement)(`div`, { className: `dsu-number` }, (0, react.createElement)(`input`, {
|
|
288
|
+
className: `dsu-input`,
|
|
289
|
+
type: `number`,
|
|
290
|
+
inputMode: `numeric`,
|
|
291
|
+
id: f,
|
|
292
|
+
value: a,
|
|
293
|
+
min: t.min,
|
|
294
|
+
max: t.max,
|
|
295
|
+
step: t.step,
|
|
296
|
+
placeholder: t.placeholder,
|
|
297
|
+
"aria-invalid": l || void 0,
|
|
298
|
+
"aria-describedby": h,
|
|
299
|
+
"data-field": t.field,
|
|
300
|
+
disabled: t.disabled === !0,
|
|
301
|
+
onChange: (e) => {
|
|
302
|
+
v(e.target.value);
|
|
303
|
+
},
|
|
304
|
+
onBlur: y
|
|
305
|
+
}), t.unit === void 0 ? null : (0, react.createElement)(`span`, { className: `dsu-unit` }, t.unit)), l ? (0, react.createElement)(`p`, {
|
|
306
|
+
className: `dsu-error`,
|
|
307
|
+
id: p
|
|
308
|
+
}, t.invalidHint) : null));
|
|
309
|
+
}
|
|
310
|
+
function y(t) {
|
|
311
|
+
let [i, a] = (0, react.useState)(t.value), [o, c] = (0, react.useState)(!1), l = (0, react.useId)(), u = t.hint === void 0 ? void 0 : `${l}-hint`;
|
|
312
|
+
(0, react.useEffect)(() => {
|
|
313
|
+
o || a(t.value);
|
|
314
|
+
}, [t.value, o]);
|
|
315
|
+
let d = {
|
|
316
|
+
id: l,
|
|
317
|
+
className: `dsu-input${t.multiline === !0 ? ` dsu-textarea` : ``}${t.monospace === !0 ? ` dsu-mono` : ``}`,
|
|
318
|
+
value: i,
|
|
319
|
+
placeholder: t.placeholder,
|
|
320
|
+
"aria-describedby": u,
|
|
321
|
+
"data-field": t.field,
|
|
322
|
+
disabled: t.disabled === !0,
|
|
323
|
+
onFocus: () => {
|
|
324
|
+
c(!0);
|
|
325
|
+
},
|
|
326
|
+
onBlur: () => {
|
|
327
|
+
c(!1);
|
|
328
|
+
},
|
|
329
|
+
onChange: (e) => {
|
|
330
|
+
a(e.target.value), t.onChange(e.target.value);
|
|
331
|
+
}
|
|
332
|
+
};
|
|
333
|
+
return (0, react.createElement)(g, {
|
|
334
|
+
...t,
|
|
335
|
+
controlId: l
|
|
336
|
+
}, t.multiline === !0 ? (0, react.createElement)(`textarea`, d) : (0, react.createElement)(`input`, d));
|
|
337
|
+
}
|
|
338
|
+
function S(t) {
|
|
339
|
+
let n = !t.writable || t.busy || !t.dirty, r;
|
|
340
|
+
return r = t.error === null ? t.writable ? t.dirty ? t.dirtyLabel : t.cleanLabel : t.readOnlyLabel : t.error, (0, react.createElement)(`div`, {
|
|
341
|
+
className: `dsu-savebar`,
|
|
342
|
+
"data-dirty": t.dirty
|
|
343
|
+
}, (0, react.createElement)(`button`, {
|
|
344
|
+
type: `button`,
|
|
345
|
+
className: `dsu-btn dsu-btnPrimary`,
|
|
346
|
+
"data-field": `save`,
|
|
347
|
+
disabled: n,
|
|
348
|
+
onClick: t.onSave
|
|
349
|
+
}, t.busy ? t.savingLabel : t.saveLabel), (0, react.createElement)(`button`, {
|
|
350
|
+
type: `button`,
|
|
351
|
+
className: `dsu-btn`,
|
|
352
|
+
"data-field": `discard`,
|
|
353
|
+
disabled: n,
|
|
354
|
+
onClick: t.onDiscard
|
|
355
|
+
}, t.revertLabel), (0, react.createElement)(`span`, { className: t.error === null ? `dsu-status` : `dsu-status dsu-statusError` }, r));
|
|
356
|
+
}
|
|
25
357
|
// src/ui-messages.ts
|
|
26
358
|
const UI_MESSAGES = {
|
|
27
359
|
zh: {
|
|
28
|
-
cardTitle: "quality-gate 回合收口质量门禁",
|
|
29
|
-
cardDescription: "回合收口质量门禁(ECC stop-format-typecheck 移植):本回合编辑过代码文件时,收口前自动跑项目门禁(根 package.json 声明了 check 才用 pnpm check,否则 cargo check / ruff / tsc),失败自动开新回合注入修复指令;失败事实同步 lesson-loop 总线,记忆网关沉淀默认关闭",
|
|
30
360
|
statusReadOnly: "当前作用域只读",
|
|
31
|
-
statusDirty: "有未保存的修改,点「保存」生效",
|
|
32
361
|
statusClean: "无未保存的修改",
|
|
33
362
|
save: "保存",
|
|
34
363
|
saving: "保存中…",
|
|
35
364
|
revert: "撤销",
|
|
36
365
|
saveFailed: "保存失败:{message}",
|
|
37
366
|
saveRejected: "保存被宿主拒绝(配置校验未通过或写入被跳过),修改未生效",
|
|
367
|
+
invalidGatewayUrl: "网关地址不接受:不能带用户名/口令;远端地址必须是 https,明文 http 只允许本机回环",
|
|
368
|
+
invalidKeyRef: "凭据引用名不接受:只能由字母、数字、下划线组成,且不能以数字开头",
|
|
38
369
|
outOfRange: "超出允许范围,未保存",
|
|
370
|
+
gateSection: "门禁",
|
|
371
|
+
memorySection: "记忆反馈",
|
|
372
|
+
unitTimes: "次",
|
|
373
|
+
unitSeconds: "秒",
|
|
374
|
+
dirtyCount: "已修改 {count} 项",
|
|
39
375
|
enabledLabel: "启用质量门禁",
|
|
40
376
|
enabledHint: "关闭后回合收口不再跑门禁(模型可带病收口)",
|
|
41
|
-
injectsLabel: "
|
|
42
|
-
injectsHint: "
|
|
43
|
-
budgetLabel: "
|
|
44
|
-
budgetHint: "
|
|
377
|
+
injectsLabel: "每会话注入配额",
|
|
378
|
+
injectsHint: "门禁失败后最多注入几次修复指令;配额按会话累计,门禁通过后重置",
|
|
379
|
+
budgetLabel: "门禁总预算",
|
|
380
|
+
budgetHint: "一次回合内所有门禁共享的总耗时预算(多根均分);超时按「未能执行」注入,不静默放行",
|
|
45
381
|
memoryLabel: "门禁失败沉淀记忆",
|
|
46
|
-
memoryHint: "默认关闭。开启后:代码型门禁失败的证据(命令/根/全文报错,不截断)写入记忆网关的 L0 会话层供后续会话召回;总线 lesson-loop
|
|
382
|
+
memoryHint: "默认关闭。开启后:代码型门禁失败的证据(命令/根/全文报错,不截断)写入记忆网关的 L0 会话层供后续会话召回;总线 lesson-loop 的同步上报不受此开关影响。关闭后只注入不沉淀。投递要求网关地址与凭据引用都可用(见下面两项)",
|
|
47
383
|
gatewayUrlLabel: "记忆网关地址",
|
|
48
|
-
gatewayUrlHint: "插件不带任何默认地址:本机用户请填自己的网关地址(例如 http://127.0.0.1:8420),没有网关就让它空着。此项留空时回落到环境变量 TDAI_GATEWAY_URL
|
|
384
|
+
gatewayUrlHint: "插件不带任何默认地址:本机用户请填自己的网关地址(例如 http://127.0.0.1:8420),没有网关就让它空着。此项留空时回落到环境变量 TDAI_GATEWAY_URL,两处都空才算未配置、不启用记忆反馈。远端地址必须 https,明文 http 只允许本机回环(127.0.0.0/8、::1、localhost)",
|
|
49
385
|
keyRefLabel: "网关密钥引用",
|
|
50
386
|
keyRefHint: "网关鉴权令牌的凭据引用名(环境变量名形态,默认 TDAI_GATEWAY_KEY)。这里存的只是引用名,密钥值由 dsh 凭据服务管理(Web 凭据页或 ~/.dsh/.credentials.yaml 的 refs),不会写进 settings.yaml;清空即恢复默认引用名,而不是停用记忆反馈"
|
|
51
387
|
},
|
|
52
388
|
en: {
|
|
53
|
-
cardTitle: "quality-gate turn-end quality gate",
|
|
54
|
-
cardDescription: "Turn-end quality gate (ported from ECC stop-format-typecheck): when this turn edited code files, the project gate runs automatically before the turn closes (pnpm check only if the root package.json declares a check script, otherwise cargo check / ruff / tsc); a failure opens a new turn with repair instructions. Failure facts are also reported to the lesson-loop bus; memory-gateway persistence is off by default",
|
|
55
389
|
statusReadOnly: "This scope is read-only",
|
|
56
|
-
statusDirty: "Unsaved changes — press Save to apply",
|
|
57
390
|
statusClean: "No unsaved changes",
|
|
58
391
|
save: "Save",
|
|
59
392
|
saving: "Saving…",
|
|
60
393
|
revert: "Revert",
|
|
61
394
|
saveFailed: "Save failed: {message}",
|
|
62
395
|
saveRejected: "Save rejected by the host (validation failed or write skipped) — changes not applied",
|
|
396
|
+
invalidGatewayUrl: "Gateway URL rejected: it must not embed credentials; remote addresses must use https, plain http is only allowed for loopback",
|
|
397
|
+
invalidKeyRef: "Credential reference rejected: use letters, digits and underscores only, and do not start with a digit",
|
|
63
398
|
outOfRange: "Out of the allowed range — not saved",
|
|
399
|
+
gateSection: "Gate",
|
|
400
|
+
memorySection: "Memory feedback",
|
|
401
|
+
unitTimes: "times",
|
|
402
|
+
unitSeconds: "seconds",
|
|
403
|
+
dirtyCount: "{count} changed",
|
|
64
404
|
enabledLabel: "Enable the quality gate",
|
|
65
405
|
enabledHint: "When off: no gate runs at turn end (the model may close a turn with broken code)",
|
|
66
|
-
injectsLabel: "Repair injections per
|
|
67
|
-
injectsHint: "How many repair instructions a failing gate may inject
|
|
68
|
-
budgetLabel: "Gate budget
|
|
69
|
-
budgetHint: "
|
|
406
|
+
injectsLabel: "Repair injections per session",
|
|
407
|
+
injectsHint: "How many repair instructions a failing gate may inject; the quota accumulates across turns in one session and resets after a clean gate pass",
|
|
408
|
+
budgetLabel: "Gate total budget",
|
|
409
|
+
budgetHint: "Shared time budget for all gates in one turn (split across project roots); a timeout is injected as \"could not run\", never as a silent pass",
|
|
70
410
|
memoryLabel: "Persist gate failures to memory",
|
|
71
|
-
memoryHint: "Off by default. When on: the evidence of code-level gate failures (command, root, full error text, untruncated) is written into the memory gateway's L0 session layer so later sessions can recall it; synchronous reporting to the lesson-loop bus is unaffected by this switch. When off, repairs are injected but nothing is persisted.
|
|
411
|
+
memoryHint: "Off by default. When on: the evidence of code-level gate failures (command, root, full error text, untruncated) is written into the memory gateway's L0 session layer so later sessions can recall it; synchronous reporting to the lesson-loop bus is unaffected by this switch. When off, repairs are injected but nothing is persisted. Delivery requires both the gateway URL and the credential ref to be usable (see the two fields below)",
|
|
72
412
|
gatewayUrlLabel: "Memory gateway URL",
|
|
73
|
-
gatewayUrlHint: "The plugin ships no default address: fill in your own gateway URL (e.g. http://127.0.0.1:8420), or leave it empty if you have no gateway. When this is empty the TDAI_GATEWAY_URL environment variable is used instead; only when both are empty is memory feedback unconfigured and disabled",
|
|
413
|
+
gatewayUrlHint: "The plugin ships no default address: fill in your own gateway URL (e.g. http://127.0.0.1:8420), or leave it empty if you have no gateway. When this is empty the TDAI_GATEWAY_URL environment variable is used instead; only when both are empty is memory feedback unconfigured and disabled. Remote addresses must use https; plain http is only allowed for loopback (127.0.0.0/8, ::1, localhost)",
|
|
74
414
|
keyRefLabel: "Gateway credential ref",
|
|
75
415
|
keyRefHint: "Name of the credential holding the gateway token (POSIX env-var form, default TDAI_GATEWAY_KEY). Only the ref name is stored here — the secret itself is managed by the dsh credentials service (the web credentials page or the refs in ~/.dsh/.credentials.yaml) and never lands in settings.yaml; clearing it restores the default ref name rather than disabling memory feedback"
|
|
76
416
|
}
|
|
77
417
|
};
|
|
418
|
+
// lib/credential-url.ts
|
|
419
|
+
/** IPv4 单段。 */
|
|
420
|
+
const IPV4_OCTET = /^\d{1,3}$/u;
|
|
421
|
+
/** IPv4-mapped IPv6 的字面前缀。 */
|
|
422
|
+
const IPV4_MAPPED_PREFIX = "::ffff:";
|
|
423
|
+
/**
|
|
424
|
+
* 是否为 IPv4-mapped 的 IPv4 回环(`::ffff:127.0.0.0/8`,URL 会归一成 `::ffff:7fxx:xxxx`)。
|
|
425
|
+
*
|
|
426
|
+
* 必须按末 32 位的**实际数值**判,不能按字符串前缀 `::ffff:7f`:前缀命中的
|
|
427
|
+
* `::ffff:7fa:0` 末 32 位是 0x07fa0000 = 7.250.0.0,并不在 127.0.0.0/8 里。按前缀放行
|
|
428
|
+
* 等于把 Bearer 令牌发往远端明文 http,与本函数的安全承诺相反(fail-open 比不判更糟)。
|
|
429
|
+
* 非规范的展开写法(如 `::ffff:0:7f00:1`)判否——fail-closed,可接受。
|
|
430
|
+
*
|
|
431
|
+
* @param host - `URL.hostname` 去掉方括号后的主机名
|
|
432
|
+
* @returns 是否为 IPv4 回环的映射写法
|
|
433
|
+
*/
|
|
434
|
+
function isIpv4MappedLoopback(host) {
|
|
435
|
+
if (!host.startsWith(IPV4_MAPPED_PREFIX)) return false;
|
|
436
|
+
const segments = host.slice(7).split(":");
|
|
437
|
+
if (segments.length !== 2) return false;
|
|
438
|
+
const high = Number.parseInt(String(segments[0]), 16);
|
|
439
|
+
return high >= 32512 && high <= 32767;
|
|
440
|
+
}
|
|
441
|
+
/**
|
|
442
|
+
* 是否为 IPv4 回环(127.0.0.0/8 整段,不只是 127.0.0.1)。
|
|
443
|
+
*
|
|
444
|
+
* @param host - `URL.hostname` 去掉方括号后的主机名
|
|
445
|
+
* @returns 是否落在 127.0.0.0/8
|
|
446
|
+
*/
|
|
447
|
+
function isIpv4Loopback(host) {
|
|
448
|
+
const parts = host.split(".");
|
|
449
|
+
if (parts.length !== 4 || parts[0] !== "127") return false;
|
|
450
|
+
return parts.every((part) => IPV4_OCTET.test(part) && Number(part) <= 255);
|
|
451
|
+
}
|
|
452
|
+
/**
|
|
453
|
+
* 凭据只允许发往 https 或回环地址:明文 http 到远端会把 Bearer 令牌暴露给同网段
|
|
454
|
+
* (设置卡默认值就是本机 http,属有意用法)。地址里带 userinfo 一律判否——那是另一条
|
|
455
|
+
* 会随 URL 一起写进设置文件的凭据形态,与本插件把密钥交给凭据服务的设计冲突。
|
|
456
|
+
*
|
|
457
|
+
* 回环按整段判定而不是三个字面量:IPv4 回环是 127.0.0.0/8,网关绑在 127.0.0.2 /
|
|
458
|
+
* 127.0.1.1 时同样只在本机,判成"不安全"会让记忆反馈静默停掉、而用户从提示里看不出
|
|
459
|
+
* 自己其实就在本机;IPv6 的 IPv4-mapped 写法(URL 把 `[::ffff:127.0.0.1]` 归一成
|
|
460
|
+
* `[::ffff:7f00:1]`)同理。
|
|
461
|
+
*
|
|
462
|
+
* `localhost` 这一支是**有意保留的 fail-open**:它依赖 hosts/DNS 解析,hosts 被指向远端时
|
|
463
|
+
* 本函数仍判安全。保留它是为了让设置卡的默认写法可用;要彻底关掉这个口子只能改成只认 IP
|
|
464
|
+
* 字面量,那会让"本机网关"这条最常见用法需要用户自己去查 IP。
|
|
465
|
+
*
|
|
466
|
+
* @param url - 待判定的完整 URL
|
|
467
|
+
* @returns 是否可以携带凭据发往该地址
|
|
468
|
+
*/
|
|
469
|
+
function isCredentialSafeUrl(url) {
|
|
470
|
+
let parsed;
|
|
471
|
+
try {
|
|
472
|
+
parsed = new URL(url);
|
|
473
|
+
} catch {
|
|
474
|
+
return false;
|
|
475
|
+
}
|
|
476
|
+
if (parsed.username !== "" || parsed.password !== "") return false;
|
|
477
|
+
if (parsed.protocol === "https:") return true;
|
|
478
|
+
if (parsed.protocol !== "http:") return false;
|
|
479
|
+
const host = parsed.hostname.replaceAll(/^\[|\]$/gu, "");
|
|
480
|
+
return host === "localhost" || isIpv4Loopback(host) || host === "::1" || isIpv4MappedLoopback(host);
|
|
481
|
+
}
|
|
78
482
|
// src/client-entry.ts
|
|
79
483
|
const NS = "quality-gate";
|
|
80
484
|
const BUNDLE_PKG = "@jayyuen66/dsh-quality-gate";
|
|
81
|
-
const CARD_CSS = [
|
|
82
|
-
".qgc-card{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:12px;list-style:none;transition:border-color .16s,background .16s}",
|
|
83
|
-
".qgc-card:hover{border-color:var(--dsw-alias-label-dimmed)}",
|
|
84
|
-
".qgc-card-open{background:var(--dsw-alias-bg-layer-2);border-color:var(--dsw-alias-label-dimmed)}",
|
|
85
|
-
".qgc-header{appearance:none;width:100%;font:inherit;color:inherit;text-align:left;cursor:pointer;background:transparent;border:0;border-radius:12px;align-items:center;gap:12px;padding:14px 16px;display:flex}",
|
|
86
|
-
".qgc-head{flex-direction:column;flex:1;gap:4px;min-width:0;display:flex}",
|
|
87
|
-
".qgc-name{color:var(--dsw-alias-label-primary);font-size:15px;font-weight:600;line-height:1.4}",
|
|
88
|
-
".qgc-desc{color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:1.5}",
|
|
89
|
-
".qgc-chevron{color:var(--dsw-alias-label-tertiary);flex:none;transition:transform .16s}",
|
|
90
|
-
".qgc-chevron-open{transform:rotate(180deg)}",
|
|
91
|
-
".qgc-body{border-top:1px solid var(--dsw-alias-border-l2);margin:0 16px;padding:8px 0 12px}",
|
|
92
|
-
".qgc-row{display:flex;flex-direction:row;justify-content:space-between;align-items:center;gap:12px;padding:9px 0}",
|
|
93
|
-
".qgc-label{font-size:13px;color:var(--dsw-alias-label-primary,inherit)}",
|
|
94
|
-
".qgc-hint{font-size:12px;color:var(--dsw-alias-label-tertiary,#8a8f99);line-height:1.5;margin-top:2px}",
|
|
95
|
-
".qgc-switch{appearance:none;position:relative;width:34px;height:20px;border-radius:10px;background:var(--dsw-alias-fill-primary,#d8dbe2);transition:background .16s;cursor:pointer;border:0;flex:none}",
|
|
96
|
-
".qgc-switch::after{content:\"\";position:absolute;top:2px;left:2px;width:16px;height:16px;border-radius:50%;background:var(--dsw-alias-bg-layer-1,#fff);transition:left .16s}",
|
|
97
|
-
".qgc-switch-on{background:var(--dsw-alias-brand-primary,#e07856)}",
|
|
98
|
-
".qgc-switch-on::after{left:16px}",
|
|
99
|
-
".qgc-switch:disabled{cursor:not-allowed;opacity:.6}",
|
|
100
|
-
".qgc-input{width:100%;box-sizing:border-box;font:inherit;font-size:12px;color:var(--dsw-alias-label-primary,inherit);background:var(--dsw-alias-bg-layer-1,transparent);border:1px solid var(--dsw-alias-border-l2,transparent);border-radius:8px;padding:6px 8px;margin-top:4px}",
|
|
101
|
-
".qgc-savebar{display:flex;gap:8px;align-items:center;padding:10px 0 2px;border-top:1px dashed var(--dsw-alias-border-l2);margin-top:6px;flex-wrap:wrap}",
|
|
102
|
-
".qgc-btn{appearance:none;font:inherit;font-size:12px;cursor:pointer;border-radius:6px;padding:4px 12px;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1,transparent);color:var(--dsw-alias-label-primary,inherit)}",
|
|
103
|
-
".qgc-btn:hover:not(:disabled){border-color:var(--dsw-alias-label-dimmed)}",
|
|
104
|
-
".qgc-btn-primary{background:var(--dsw-alias-brand-primary,#e07856);border-color:var(--dsw-alias-brand-primary,#e07856);color:var(--dsw-alias-label-primary-foreground,#fff)}",
|
|
105
|
-
".qgc-btn:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#e07856);outline-offset:1px}",
|
|
106
|
-
".qgc-btn:disabled{opacity:.5;cursor:not-allowed}",
|
|
107
|
-
".qgc-dirty{font-size:12px;color:var(--dsw-alias-label-tertiary,#8a8f99)}",
|
|
108
|
-
".qgc-saveerr{font-size:12px;color:#c4483f}"
|
|
109
|
-
].join("\n");
|
|
110
|
-
function ToggleRow(props) {
|
|
111
|
-
return (0, react.createElement)("div", { className: "qgc-row" }, (0, react.createElement)("div", null, (0, react.createElement)("div", { className: "qgc-label" }, props.label), (0, react.createElement)("div", { className: "qgc-hint" }, props.hint)), (0, react.createElement)("button", {
|
|
112
|
-
type: "button",
|
|
113
|
-
className: `qgc-switch${props.checked ? " qgc-switch-on" : ""}`,
|
|
114
|
-
role: "switch",
|
|
115
|
-
"aria-checked": props.checked,
|
|
116
|
-
"data-field": props.field,
|
|
117
|
-
disabled: props.disabled === true,
|
|
118
|
-
onClick: props.onToggle
|
|
119
|
-
}));
|
|
120
|
-
}
|
|
121
|
-
/** 数字输入行:settings 数字字段 ↔ UI 字符串(v7 配置面补齐)。 */
|
|
122
|
-
function NumberInputRow(props) {
|
|
123
|
-
const num = typeof props.value === "number" && Number.isFinite(props.value) ? String(props.value) : "";
|
|
124
|
-
const [text, setText] = (0, react.useState)(num);
|
|
125
|
-
const [invalid, setInvalid] = (0, react.useState)(false);
|
|
126
|
-
(0, react.useEffect)(() => {
|
|
127
|
-
setText(num);
|
|
128
|
-
setInvalid(false);
|
|
129
|
-
}, [num]);
|
|
130
|
-
const invalidHint = props.t("outOfRange");
|
|
131
|
-
return (0, react.createElement)("div", { className: "qgc-row" }, (0, react.createElement)("div", null, (0, react.createElement)("div", { className: "qgc-label" }, props.label), (0, react.createElement)("div", { className: "qgc-hint" }, props.hint), (0, react.createElement)("input", {
|
|
132
|
-
type: "number",
|
|
133
|
-
className: "qgc-input",
|
|
134
|
-
value: text,
|
|
135
|
-
disabled: props.disabled === true,
|
|
136
|
-
"aria-invalid": invalid || void 0,
|
|
137
|
-
onChange: (event) => {
|
|
138
|
-
const next = event.target.value;
|
|
139
|
-
setText(next);
|
|
140
|
-
if (next === "") {
|
|
141
|
-
setInvalid(false);
|
|
142
|
-
props.onClear(props.field);
|
|
143
|
-
return;
|
|
144
|
-
}
|
|
145
|
-
const numeric = Number(next);
|
|
146
|
-
if (!Number.isFinite(numeric)) {
|
|
147
|
-
setInvalid(true);
|
|
148
|
-
return;
|
|
149
|
-
}
|
|
150
|
-
const min = props.min ?? -Infinity;
|
|
151
|
-
const max = props.max ?? Infinity;
|
|
152
|
-
if (numeric >= min && numeric <= max) {
|
|
153
|
-
setInvalid(false);
|
|
154
|
-
props.onChange(props.field, numeric);
|
|
155
|
-
} else setInvalid(true);
|
|
156
|
-
}
|
|
157
|
-
}), invalid ? (0, react.createElement)("div", {
|
|
158
|
-
className: "qgc-hint",
|
|
159
|
-
style: { color: "var(--dsw-alias-danger, #e5484d)" }
|
|
160
|
-
}, invalidHint) : null));
|
|
161
|
-
}
|
|
162
485
|
/**
|
|
163
|
-
*
|
|
164
|
-
*
|
|
486
|
+
* 两个数值字段的闭区间只写在 zod schema 上:host schema(`Schema.natural().min/max`)
|
|
487
|
+
* 与卡片输入框都从这里取。zod 4.6 的 `.minValue`/`.maxValue` 直接读出折叠后的边界,
|
|
488
|
+
* `NumberField` 的 `min/max` 因此不再另抄一份数字。
|
|
165
489
|
*/
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
className: "qgc-input",
|
|
175
|
-
value: draft,
|
|
176
|
-
placeholder: props.placeholder ?? "",
|
|
177
|
-
disabled: props.disabled === true,
|
|
178
|
-
onChange: (event) => {
|
|
179
|
-
const next = event.target.value;
|
|
180
|
-
setDraft(next);
|
|
181
|
-
if (next.trim() === "") {
|
|
182
|
-
props.onClear(props.field);
|
|
183
|
-
return;
|
|
184
|
-
}
|
|
185
|
-
props.onChange(props.field, next.trim());
|
|
186
|
-
}
|
|
187
|
-
})));
|
|
490
|
+
const MAX_INJECTS_VALUE = zod.z.int().min(1).max(5);
|
|
491
|
+
const GATE_BUDGET_MS_VALUE = zod.z.int().min(1e4).max(6e5);
|
|
492
|
+
/** 把 zod 折叠后的显式边界摊成可选 props(null = 该方向没声明,不伪造一个上限)。 */
|
|
493
|
+
function boundsOf(schema) {
|
|
494
|
+
const bounds = {};
|
|
495
|
+
if (schema.minValue !== null) bounds.min = schema.minValue;
|
|
496
|
+
if (schema.maxValue !== null) bounds.max = schema.maxValue;
|
|
497
|
+
return bounds;
|
|
188
498
|
}
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
499
|
+
const MAX_INJECTS_BOUNDS = boundsOf(MAX_INJECTS_VALUE);
|
|
500
|
+
const GATE_BUDGET_MS_BOUNDS = boundsOf(GATE_BUDGET_MS_VALUE);
|
|
501
|
+
/** 两个数值 schema 的对外投影:测试用 `.minValue`/`.maxValue` 与 `z.toJSONSchema` 钉边界。 */
|
|
502
|
+
const CARD_NUMBER_SCHEMAS = {
|
|
503
|
+
maxInjectsPerTurn: MAX_INJECTS_VALUE,
|
|
504
|
+
gateBudgetMs: GATE_BUDGET_MS_VALUE
|
|
505
|
+
};
|
|
506
|
+
/** 版本可移植的闭区间整数判:宿主平台冻结的 zod(实测 4.4.3)没有 `.validate`
|
|
507
|
+
* (4.6 才回到 classic 面),判据用 `boundsOf` 折出的边界自持,不押 zod 的方法面——
|
|
508
|
+
* 否则单仓测试(catalog 4.6.7)全绿、真宿主里槽位崩溃、设置表单静默消失。 */
|
|
509
|
+
function inBounds(bounds, value) {
|
|
510
|
+
return Number.isInteger(value) && (bounds.min ?? Number.NEGATIVE_INFINITY) <= value && value <= (bounds.max ?? Number.POSITIVE_INFINITY);
|
|
511
|
+
}
|
|
512
|
+
/** 可选整数:越界/小数/非有限值折成 undefined,只丢那一格而不是整份快照。
|
|
513
|
+
* 不引 `z.compile`:编译器给客户端 bundle 增约 28 KB,且 CSP 环境会拒绝 `new Function`;
|
|
514
|
+
* 卡片每帧只解析一枚快照对象,收益抵不上这两条代价。 */
|
|
515
|
+
const MAX_INJECTS_SCHEMA = zod.z.preprocess((value) => typeof value === "number" && inBounds(MAX_INJECTS_BOUNDS, value) ? value : void 0, MAX_INJECTS_VALUE.optional());
|
|
516
|
+
const GATE_BUDGET_MS_SCHEMA = zod.z.preprocess((value) => typeof value === "number" && inBounds(GATE_BUDGET_MS_BOUNDS, value) ? value : void 0, GATE_BUDGET_MS_VALUE.optional());
|
|
517
|
+
/** 卡片可编辑字段的值面。逐字段先把错误类型折成 undefined:宿主给了坏形状时只丢掉
|
|
518
|
+
* 那一格,其余字段照常渲染;整份快照不是对象时才整体回落到空表。 */
|
|
519
|
+
const optionalBoolean = zod.z.preprocess((value) => typeof value === "boolean" ? value : void 0, zod.z.boolean().optional());
|
|
520
|
+
const optionalString = zod.z.preprocess((value) => typeof value === "string" ? value : void 0, zod.z.string().optional());
|
|
521
|
+
/** 网关地址:必须是 http(s),且与宿主运行期同一份判据——不能带 userinfo,远端只能 https,
|
|
522
|
+
* 明文 http 只允许本机回环。空串与坏文本折成未设置。 */
|
|
523
|
+
const optionalHttpUrl = zod.z.preprocess((value) => {
|
|
524
|
+
let text;
|
|
525
|
+
if (typeof value === "string") {
|
|
526
|
+
const trimmed = value.trim();
|
|
527
|
+
if (trimmed !== "") text = trimmed;
|
|
528
|
+
}
|
|
529
|
+
return text;
|
|
530
|
+
}, zod.z.string().refine(isCredentialSafeUrl, { error: "credential-unsafe-url" }).optional());
|
|
531
|
+
const CARD_VALUES = zod.z.object({
|
|
532
|
+
enabled: optionalBoolean,
|
|
533
|
+
maxInjectsPerTurn: MAX_INJECTS_SCHEMA,
|
|
534
|
+
gateBudgetMs: GATE_BUDGET_MS_SCHEMA,
|
|
535
|
+
memoryFeedback: optionalBoolean,
|
|
536
|
+
memoryGatewayUrl: optionalHttpUrl,
|
|
537
|
+
memoryGatewayKeyRef: optionalString
|
|
538
|
+
});
|
|
539
|
+
/** 草稿面仍按宽 string 读:`TextField` 没有 invalid 态,输入中的半截地址不会被当场折成空串;
|
|
540
|
+
* 提交时由 `writeTouched` 用同一份凭据地址判据拦下,不合规的地址不落盘。 */
|
|
541
|
+
const CARD_FIELD_SCHEMAS = {
|
|
542
|
+
enabled: optionalBoolean,
|
|
543
|
+
maxInjectsPerTurn: MAX_INJECTS_SCHEMA,
|
|
544
|
+
gateBudgetMs: GATE_BUDGET_MS_SCHEMA,
|
|
545
|
+
memoryFeedback: optionalBoolean,
|
|
546
|
+
memoryGatewayUrl: optionalString,
|
|
547
|
+
memoryGatewayKeyRef: optionalString
|
|
548
|
+
};
|
|
549
|
+
/** 快照值投影:逐字段解析;坏形状与越界值只折掉那一格,其余字段照常渲染。
|
|
550
|
+
* 整份一次 `safeParse` 会让一个坏 URL 把 enabled 与两个数字字段一起清空。 */
|
|
551
|
+
function projectCardValues(raw) {
|
|
552
|
+
const out = {};
|
|
553
|
+
for (const [field, schema] of Object.entries(CARD_VALUES.shape)) {
|
|
554
|
+
const parsed = schema.safeParse(raw[field]);
|
|
555
|
+
if (parsed.success) out[field] = parsed.data;
|
|
556
|
+
}
|
|
557
|
+
return out;
|
|
208
558
|
}
|
|
209
559
|
/** 值语义相等(与 shared 的 diffTouched 同口径):表单值是标量与数组,引用相等会把等值数组判成改动。 */
|
|
210
560
|
function sameValue(left, right) {
|
|
211
561
|
return JSON.stringify(left ?? null) === JSON.stringify(right ?? null);
|
|
212
562
|
}
|
|
563
|
+
/** 凭据引用名形状:与 @deepseek-ai/dsh-credentials 的 REF_PATTERN 同式。客户端不引该包的
|
|
564
|
+
* 值导出(那是宿主侧凭据服务面),所以这里按同一正则复判;两处失配时宿主运行期仍会拒绝。 */
|
|
565
|
+
const CREDENTIAL_REF_PATTERN = /^[A-Za-z_]\w*$/u;
|
|
213
566
|
/**
|
|
214
567
|
* 逐键 await 写入并**消费受理位**(0.1.7 的 set/unset 回 Promise<boolean>:
|
|
215
568
|
* true=宿主受理;false=拒绝或写入被跳过(官方语义:"refuses stale revisions";
|
|
@@ -225,6 +578,16 @@ async function writeTouched(channels) {
|
|
|
225
578
|
try {
|
|
226
579
|
for (const key of keys) {
|
|
227
580
|
const val = touched[key];
|
|
581
|
+
if (key === "memoryGatewayUrl" && typeof val === "string" && val.trim() !== "" && !isCredentialSafeUrl(val.trim())) {
|
|
582
|
+
setBusy(false);
|
|
583
|
+
setSaveError(t("invalidGatewayUrl"));
|
|
584
|
+
return;
|
|
585
|
+
}
|
|
586
|
+
if (key === "memoryGatewayKeyRef" && typeof val === "string" && val.trim() !== "" && !CREDENTIAL_REF_PATTERN.test(val.trim())) {
|
|
587
|
+
setBusy(false);
|
|
588
|
+
setSaveError(t("invalidKeyRef"));
|
|
589
|
+
return;
|
|
590
|
+
}
|
|
228
591
|
if (!await (val === void 0 ? unset(key) : set(key, val))) {
|
|
229
592
|
setBusy(false);
|
|
230
593
|
setSaveError(t("saveRejected"));
|
|
@@ -243,162 +606,125 @@ async function writeTouched(channels) {
|
|
|
243
606
|
setSaveError(t("saveFailed", { message: String(error instanceof Error ? error.message : error) }));
|
|
244
607
|
}
|
|
245
608
|
}
|
|
246
|
-
/**
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
const
|
|
250
|
-
const
|
|
251
|
-
const
|
|
252
|
-
const
|
|
253
|
-
const
|
|
254
|
-
const
|
|
255
|
-
["d"]: chevronPathData,
|
|
256
|
-
fill: "none",
|
|
257
|
-
stroke: "currentColor",
|
|
258
|
-
strokeWidth: 1.5,
|
|
259
|
-
strokeLinecap: "round",
|
|
260
|
-
strokeLinejoin: "round"
|
|
261
|
-
});
|
|
262
|
-
const chevron = (0, react.createElement)("svg", {
|
|
263
|
-
width: 14,
|
|
264
|
-
height: 14,
|
|
265
|
-
viewBox: "0 0 14 14",
|
|
266
|
-
"aria-hidden": true,
|
|
267
|
-
className: `qgc-chevron${open ? " qgc-chevron-open" : ""}`
|
|
268
|
-
}, chevronPath);
|
|
269
|
-
return (0, react.createElement)("button", {
|
|
270
|
-
type: "button",
|
|
271
|
-
className: "qgc-header",
|
|
272
|
-
"aria-expanded": open,
|
|
273
|
-
onClick: onToggle
|
|
274
|
-
}, headDiv, chevron);
|
|
275
|
-
}
|
|
276
|
-
/** 展开态的 `ul` 本体:行元素仍按从前的顺序逐个传给同一个 createElement(不折成数组
|
|
277
|
-
* 子节点,保持宿主侧 children 形状一致)。 */
|
|
278
|
-
function buildCardBody(props) {
|
|
279
|
-
const { t, writable, eff, setField, clearField, dirty, busy, saveError, onSave, onDiscard } = props;
|
|
280
|
-
const rowEnabled = (0, react.createElement)(ToggleRow, {
|
|
609
|
+
/** 门禁小节:总开关 + 注入配额 + 预算(毫秒按秒显示)。 */
|
|
610
|
+
function buildGateSection(props) {
|
|
611
|
+
const { t, writable, eff, setField } = props;
|
|
612
|
+
const maxInjects = eff("maxInjectsPerTurn");
|
|
613
|
+
const budgetMs = eff("gateBudgetMs");
|
|
614
|
+
const budgetSeconds = budgetMs === void 0 ? void 0 : budgetMs / 1e3;
|
|
615
|
+
const budgetMinSeconds = (GATE_BUDGET_MS_BOUNDS.min ?? 0) / 1e3;
|
|
616
|
+
const budgetMaxSeconds = (GATE_BUDGET_MS_BOUNDS.max ?? 0) / 1e3;
|
|
617
|
+
const enabled = (0, react.createElement)(_, {
|
|
281
618
|
label: t("enabledLabel"),
|
|
282
|
-
|
|
619
|
+
...l(t("enabledHint")),
|
|
283
620
|
field: "enabled",
|
|
284
|
-
checked: eff("enabled")
|
|
621
|
+
checked: eff("enabled") ?? true,
|
|
285
622
|
disabled: !writable,
|
|
286
|
-
|
|
287
|
-
setField("enabled",
|
|
623
|
+
onChange: (next) => {
|
|
624
|
+
setField("enabled", next);
|
|
288
625
|
}
|
|
289
626
|
});
|
|
290
|
-
const
|
|
291
|
-
t,
|
|
627
|
+
const injects = (0, react.createElement)(v, {
|
|
292
628
|
label: t("injectsLabel"),
|
|
293
|
-
|
|
629
|
+
...l(t("injectsHint")),
|
|
294
630
|
field: "maxInjectsPerTurn",
|
|
295
|
-
value:
|
|
631
|
+
value: maxInjects,
|
|
632
|
+
...MAX_INJECTS_BOUNDS,
|
|
633
|
+
step: 1,
|
|
634
|
+
unit: t("unitTimes"),
|
|
635
|
+
invalidHint: t("outOfRange"),
|
|
296
636
|
disabled: !writable,
|
|
297
|
-
onChange: (
|
|
298
|
-
setField(
|
|
299
|
-
}
|
|
300
|
-
onClear: (field) => {
|
|
301
|
-
clearField(field);
|
|
302
|
-
},
|
|
303
|
-
min: 1,
|
|
304
|
-
max: 5
|
|
637
|
+
onChange: (next) => {
|
|
638
|
+
setField("maxInjectsPerTurn", next);
|
|
639
|
+
}
|
|
305
640
|
});
|
|
306
|
-
const
|
|
307
|
-
t,
|
|
641
|
+
const budget = (0, react.createElement)(v, {
|
|
308
642
|
label: t("budgetLabel"),
|
|
309
|
-
|
|
643
|
+
...l(t("budgetHint")),
|
|
310
644
|
field: "gateBudgetMs",
|
|
311
|
-
value:
|
|
645
|
+
value: budgetSeconds,
|
|
646
|
+
min: budgetMinSeconds,
|
|
647
|
+
max: budgetMaxSeconds,
|
|
648
|
+
step: 1,
|
|
649
|
+
unit: t("unitSeconds"),
|
|
650
|
+
invalidHint: t("outOfRange"),
|
|
312
651
|
disabled: !writable,
|
|
313
|
-
onChange: (
|
|
314
|
-
setField(
|
|
315
|
-
}
|
|
316
|
-
onClear: (field) => {
|
|
317
|
-
clearField(field);
|
|
318
|
-
},
|
|
319
|
-
min: 1e4,
|
|
320
|
-
max: 6e5
|
|
652
|
+
onChange: (next) => {
|
|
653
|
+
setField("gateBudgetMs", next === void 0 ? void 0 : next * 1e3);
|
|
654
|
+
}
|
|
321
655
|
});
|
|
322
|
-
|
|
656
|
+
return (0, react.createElement)(d, { title: t("gateSection") }, enabled, injects, budget);
|
|
657
|
+
}
|
|
658
|
+
/** 记忆反馈小节:开关 + 网关地址 + 密钥引用。 */
|
|
659
|
+
function buildMemorySection(props) {
|
|
660
|
+
const { t, writable, eff, setField } = props;
|
|
661
|
+
const gatewayUrl = eff("memoryGatewayUrl") ?? "";
|
|
662
|
+
const keyRef = eff("memoryGatewayKeyRef") ?? "";
|
|
663
|
+
const memory = (0, react.createElement)(_, {
|
|
323
664
|
label: t("memoryLabel"),
|
|
324
|
-
|
|
665
|
+
...l(t("memoryHint")),
|
|
325
666
|
field: "memoryFeedback",
|
|
326
667
|
checked: eff("memoryFeedback") === true,
|
|
327
668
|
disabled: !writable,
|
|
328
|
-
|
|
329
|
-
setField("memoryFeedback",
|
|
669
|
+
onChange: (next) => {
|
|
670
|
+
setField("memoryFeedback", next);
|
|
330
671
|
}
|
|
331
672
|
});
|
|
332
|
-
const
|
|
673
|
+
const url = (0, react.createElement)(y, {
|
|
333
674
|
label: t("gatewayUrlLabel"),
|
|
334
|
-
|
|
675
|
+
...l(t("gatewayUrlHint")),
|
|
335
676
|
field: "memoryGatewayUrl",
|
|
336
|
-
value:
|
|
677
|
+
value: gatewayUrl,
|
|
337
678
|
placeholder: "http://127.0.0.1:8420",
|
|
679
|
+
monospace: true,
|
|
338
680
|
disabled: !writable,
|
|
339
|
-
onChange: (
|
|
340
|
-
setField(
|
|
341
|
-
},
|
|
342
|
-
onClear: (field) => {
|
|
343
|
-
clearField(field);
|
|
681
|
+
onChange: (next) => {
|
|
682
|
+
setField("memoryGatewayUrl", next.trim() === "" ? void 0 : next.trim());
|
|
344
683
|
}
|
|
345
684
|
});
|
|
346
|
-
const
|
|
685
|
+
const keyRefField = (0, react.createElement)(y, {
|
|
347
686
|
label: t("keyRefLabel"),
|
|
348
|
-
|
|
687
|
+
...l(t("keyRefHint")),
|
|
349
688
|
field: "memoryGatewayKeyRef",
|
|
350
|
-
value:
|
|
689
|
+
value: keyRef,
|
|
351
690
|
placeholder: "TDAI_GATEWAY_KEY",
|
|
691
|
+
monospace: true,
|
|
352
692
|
disabled: !writable,
|
|
353
|
-
onChange: (
|
|
354
|
-
setField(
|
|
355
|
-
},
|
|
356
|
-
onClear: (field) => {
|
|
357
|
-
clearField(field);
|
|
693
|
+
onChange: (next) => {
|
|
694
|
+
setField("memoryGatewayKeyRef", next.trim() === "" ? void 0 : next.trim());
|
|
358
695
|
}
|
|
359
696
|
});
|
|
360
|
-
|
|
361
|
-
t,
|
|
362
|
-
dirty,
|
|
363
|
-
writable,
|
|
364
|
-
busy,
|
|
365
|
-
error: saveError,
|
|
366
|
-
onSave,
|
|
367
|
-
onDiscard
|
|
368
|
-
});
|
|
369
|
-
return (0, react.createElement)("ul", { className: "qgc-body" }, rowEnabled, rowInjects, rowBudget, rowMemory, rowGatewayUrl, rowGatewayKeyRef, rowSave);
|
|
697
|
+
return (0, react.createElement)(d, { title: t("memorySection") }, memory, url, keyRefField);
|
|
370
698
|
}
|
|
371
699
|
function DgCard(props) {
|
|
372
|
-
const { t
|
|
373
|
-
const [open, setOpen] = (0, react.useState)(false);
|
|
700
|
+
const { t } = props;
|
|
374
701
|
const snap = props.useCard((cardSnap) => cardSnap);
|
|
375
|
-
const
|
|
702
|
+
const value = projectCardValues(snap.value);
|
|
376
703
|
const writable = snap.status === "ready" && snap.writable;
|
|
377
704
|
const [touched, setTouched] = (0, react.useState)({});
|
|
378
705
|
const [busy, setBusy] = (0, react.useState)(false);
|
|
379
706
|
const [saveError, setSaveError] = (0, react.useState)(null);
|
|
380
|
-
/** 渲染取值:touched
|
|
381
|
-
const eff = (field) =>
|
|
382
|
-
|
|
707
|
+
/** 渲染取值:touched 优先(经 zod 收窄),快照兜底(未触碰字段实时跟随外部快照)。 */
|
|
708
|
+
const eff = (field) => {
|
|
709
|
+
if (!(field in touched)) return value[field];
|
|
710
|
+
const parsedTouched = CARD_FIELD_SCHEMAS[field].safeParse(touched[field]);
|
|
711
|
+
return parsedTouched.success ? parsedTouched.data : void 0;
|
|
712
|
+
};
|
|
713
|
+
const dirtyKeys = t$1(touched, value);
|
|
714
|
+
const dirty = dirtyKeys.length > 0;
|
|
383
715
|
const setField = (field, val) => {
|
|
384
716
|
setTouched((prev) => ({
|
|
385
717
|
...prev,
|
|
386
718
|
[field]: val
|
|
387
719
|
}));
|
|
388
720
|
};
|
|
389
|
-
const clearField = (field) => {
|
|
390
|
-
setTouched((prev) => ({
|
|
391
|
-
...prev,
|
|
392
|
-
[field]: void 0
|
|
393
|
-
}));
|
|
394
|
-
};
|
|
395
721
|
const save = () => {
|
|
396
|
-
const keys = t(touched, value);
|
|
722
|
+
const keys = t$1(touched, value);
|
|
397
723
|
if (keys.length === 0) return;
|
|
398
724
|
setBusy(true);
|
|
399
725
|
setSaveError(null);
|
|
400
726
|
writeTouched({
|
|
401
|
-
t
|
|
727
|
+
t,
|
|
402
728
|
set: props.set,
|
|
403
729
|
unset: props.unset,
|
|
404
730
|
keys,
|
|
@@ -412,26 +738,29 @@ function DgCard(props) {
|
|
|
412
738
|
setTouched({});
|
|
413
739
|
setSaveError(null);
|
|
414
740
|
};
|
|
415
|
-
const
|
|
416
|
-
t
|
|
417
|
-
open,
|
|
418
|
-
onToggle: () => {
|
|
419
|
-
setOpen(!open);
|
|
420
|
-
}
|
|
421
|
-
});
|
|
422
|
-
const body = open ? buildCardBody({
|
|
423
|
-
t: t$1,
|
|
741
|
+
const inputs = {
|
|
742
|
+
t,
|
|
424
743
|
writable,
|
|
425
744
|
eff,
|
|
426
|
-
setField
|
|
427
|
-
|
|
745
|
+
setField
|
|
746
|
+
};
|
|
747
|
+
const gate = buildGateSection(inputs);
|
|
748
|
+
const memory = buildMemorySection(inputs);
|
|
749
|
+
const saveBar = (0, react.createElement)(S, {
|
|
428
750
|
dirty,
|
|
429
751
|
busy,
|
|
430
|
-
|
|
752
|
+
writable,
|
|
753
|
+
error: saveError,
|
|
754
|
+
saveLabel: t("save"),
|
|
755
|
+
savingLabel: t("saving"),
|
|
756
|
+
revertLabel: t("revert"),
|
|
757
|
+
dirtyLabel: t("dirtyCount", { count: dirtyKeys.length }),
|
|
758
|
+
cleanLabel: t("statusClean"),
|
|
759
|
+
readOnlyLabel: t("statusReadOnly"),
|
|
431
760
|
onSave: save,
|
|
432
761
|
onDiscard: discard
|
|
433
|
-
})
|
|
434
|
-
return (0, react.createElement)(
|
|
762
|
+
});
|
|
763
|
+
return (0, react.createElement)(u, null, gate, memory, saveBar);
|
|
435
764
|
}
|
|
436
765
|
function cardStore(scope) {
|
|
437
766
|
let cachedSnap = null;
|
|
@@ -467,8 +796,8 @@ const inject = [
|
|
|
467
796
|
function apply(ctx) {
|
|
468
797
|
ctx.effect(() => {
|
|
469
798
|
const tag = document.createElement("style");
|
|
470
|
-
tag.id = "
|
|
471
|
-
tag.textContent =
|
|
799
|
+
tag.id = "dsh-card-ui-css-quality-gate";
|
|
800
|
+
tag.textContent = c;
|
|
472
801
|
document.head.append(tag);
|
|
473
802
|
return () => {
|
|
474
803
|
tag.remove();
|
|
@@ -491,8 +820,11 @@ function apply(ctx) {
|
|
|
491
820
|
}, DgCard);
|
|
492
821
|
});
|
|
493
822
|
}
|
|
823
|
+
exports.CARD_NUMBER_SCHEMAS = CARD_NUMBER_SCHEMAS;
|
|
494
824
|
exports.apply = apply;
|
|
495
825
|
exports.inject = inject;
|
|
826
|
+
exports.projectCardValues = projectCardValues;
|
|
827
|
+
exports.writeTouched = writeTouched;
|
|
496
828
|
return module.exports;
|
|
497
829
|
},
|
|
498
830
|
});
|