@dsh-plus/access-gate 0.1.2 → 0.1.4
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/lib/client.js +79 -38
- package/package.json +8 -9
- package/src/client/card.tsx +9 -16
- package/src/client/client.ts +13 -13
package/lib/client.js
CHANGED
|
@@ -6,6 +6,25 @@ window.__ModuleLoader__.load({
|
|
|
6
6
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
7
7
|
let react_jsx_runtime = require("react/jsx-runtime");
|
|
8
8
|
let react = require("react");
|
|
9
|
+
//#region ../shared/src/client/icons.tsx
|
|
10
|
+
/** 官方 IconChevronDownOutline14 的同构实现(path 数据逐字一致)。 */
|
|
11
|
+
function ChevronDownIcon(props) {
|
|
12
|
+
const size = props.size ?? 14;
|
|
13
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
|
|
14
|
+
width: size,
|
|
15
|
+
height: size,
|
|
16
|
+
className: props.className,
|
|
17
|
+
viewBox: "0 0 14 14",
|
|
18
|
+
fill: "none",
|
|
19
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
20
|
+
"aria-hidden": "true",
|
|
21
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
22
|
+
d: "M11.8486 5.5L11.4238 5.92383L8.69727 8.65137C8.44157 8.90706 8.21562 9.13382 8.01172 9.29785C7.79912 9.46883 7.55595 9.61756 7.25 9.66602C7.08435 9.69222 6.91565 9.69222 6.75 9.66602C6.44405 9.61756 6.20088 9.46883 5.98828 9.29785C5.78438 9.13382 5.55843 8.90706 5.30273 8.65137L2.57617 5.92383L2.15137 5.5L3 4.65137L3.42383 5.07617L6.15137 7.80273C6.42595 8.07732 6.59876 8.24849 6.74023 8.3623C6.87291 8.46904 6.92272 8.47813 6.9375 8.48047C6.97895 8.48703 7.02105 8.48703 7.0625 8.48047C7.07728 8.47813 7.12709 8.46904 7.25977 8.3623C7.40124 8.24849 7.57405 8.07732 7.84863 7.80273L10.5762 5.07617L11 4.65137L11.8486 5.5Z",
|
|
23
|
+
fill: "currentColor"
|
|
24
|
+
})
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
//#endregion
|
|
9
28
|
//#region ../shared/src/client/card.tsx
|
|
10
29
|
const IDLE_STATUS = {
|
|
11
30
|
kind: "idle",
|
|
@@ -41,10 +60,7 @@ window.__ModuleLoader__.load({
|
|
|
41
60
|
className: `${p}-pending`,
|
|
42
61
|
children: props.dirtyLabel
|
|
43
62
|
}) : null,
|
|
44
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("
|
|
45
|
-
className: `${p}-chevron${props.open ? ` ${p}-chevronOpen` : ""}`,
|
|
46
|
-
children: "▾"
|
|
47
|
-
})
|
|
63
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ChevronDownIcon, { className: `${p}-chevron${props.open ? ` ${p}-chevronOpen` : ""}` })
|
|
48
64
|
]
|
|
49
65
|
}), props.open ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
50
66
|
className: `${p}-body`,
|
|
@@ -110,6 +126,11 @@ window.__ModuleLoader__.load({
|
|
|
110
126
|
]
|
|
111
127
|
});
|
|
112
128
|
}
|
|
129
|
+
/**
|
|
130
|
+
* 勾选行:自绘圆角扁平开关(switch),对齐官方 WebUI 无原生 checkbox 的
|
|
131
|
+
* 设计语言(官方选择类控件全走胶囊/菜单,见 cardCss 的 -switch 规则)。
|
|
132
|
+
* 语义保持 checkbox(无障碍树与表单语义不变),视觉自绘。
|
|
133
|
+
*/
|
|
113
134
|
function CheckRow(props) {
|
|
114
135
|
const p = props.prefix ?? "dshp";
|
|
115
136
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
@@ -117,6 +138,8 @@ window.__ModuleLoader__.load({
|
|
|
117
138
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
118
139
|
id: props.id,
|
|
119
140
|
type: "checkbox",
|
|
141
|
+
role: "switch",
|
|
142
|
+
"aria-checked": props.checked,
|
|
120
143
|
checked: props.checked,
|
|
121
144
|
disabled: props.disabled === true,
|
|
122
145
|
onChange: (event) => props.onEdit(event.target.checked)
|
|
@@ -173,14 +196,15 @@ window.__ModuleLoader__.load({
|
|
|
173
196
|
//#endregion
|
|
174
197
|
//#region ../shared/src/client/scope.ts
|
|
175
198
|
/**
|
|
176
|
-
*
|
|
199
|
+
* remote.settings 直连实现的命名空间 scope(官方配置页 tab 同款模式)。
|
|
177
200
|
* 读:describe 取命名空间视图(脱敏 value/revision + 顶层 writable);
|
|
178
201
|
* 刷新:remote `settings/document-updated`(按命名空间过滤)与
|
|
179
202
|
* `connection/reset`;generation 防旧读覆盖新发布。任何页面 origin
|
|
180
203
|
* (loopback 或 tailnet 信任域名)下行为一致。
|
|
181
204
|
* effect 归属调用方 fiber(label 需含插件名以便诊断)。
|
|
182
205
|
*/
|
|
183
|
-
function
|
|
206
|
+
function createSettingsScope(c, ns, label) {
|
|
207
|
+
const remote = c.get("remote");
|
|
184
208
|
let state = {
|
|
185
209
|
status: "loading",
|
|
186
210
|
value: void 0,
|
|
@@ -195,36 +219,37 @@ window.__ModuleLoader__.load({
|
|
|
195
219
|
};
|
|
196
220
|
const load = async () => {
|
|
197
221
|
const gen = ++generation;
|
|
198
|
-
let
|
|
222
|
+
let view;
|
|
199
223
|
try {
|
|
200
|
-
response = await
|
|
224
|
+
const response = await remote.settings.describe();
|
|
225
|
+
if (!response.ok) return;
|
|
226
|
+
view = response.value;
|
|
201
227
|
} catch {
|
|
202
228
|
return;
|
|
203
229
|
}
|
|
204
|
-
if (gen !== generation
|
|
205
|
-
const
|
|
206
|
-
|
|
207
|
-
if (view === void 0) {
|
|
230
|
+
if (gen !== generation) return;
|
|
231
|
+
const namespace = view.namespaces.find((candidate) => candidate.ns === ns);
|
|
232
|
+
if (namespace === void 0) {
|
|
208
233
|
publish({
|
|
209
234
|
status: "unavailable",
|
|
210
235
|
value: void 0,
|
|
211
236
|
revision: void 0,
|
|
212
|
-
writable
|
|
237
|
+
writable: view.writable
|
|
213
238
|
});
|
|
214
239
|
return;
|
|
215
240
|
}
|
|
216
241
|
publish({
|
|
217
242
|
status: "ready",
|
|
218
|
-
value:
|
|
219
|
-
revision:
|
|
220
|
-
writable
|
|
243
|
+
value: namespace.value,
|
|
244
|
+
revision: namespace.revision,
|
|
245
|
+
writable: view.writable
|
|
221
246
|
});
|
|
222
247
|
};
|
|
223
248
|
const refresh = (namespace) => {
|
|
224
249
|
if (namespace !== void 0 && namespace !== ns) return;
|
|
225
250
|
load();
|
|
226
251
|
};
|
|
227
|
-
const disposers = [
|
|
252
|
+
const disposers = [remote.$on("settings/document-updated", refresh), c.on("connection/reset", () => {
|
|
228
253
|
load();
|
|
229
254
|
})];
|
|
230
255
|
c.effect(() => () => {
|
|
@@ -242,6 +267,20 @@ window.__ModuleLoader__.load({
|
|
|
242
267
|
load
|
|
243
268
|
};
|
|
244
269
|
}
|
|
270
|
+
/** 以 remote.settings 构造命名空间绑定的 api 面。 */
|
|
271
|
+
function createNamespaceApi(remote, ns) {
|
|
272
|
+
return {
|
|
273
|
+
update: async (patch, expectedRevision) => {
|
|
274
|
+
const response = await remote.update(ns, patch, expectedRevision);
|
|
275
|
+
if (!response.ok) throw new Error(response.error.message ?? "settings update failed");
|
|
276
|
+
},
|
|
277
|
+
describe: async () => {
|
|
278
|
+
const response = await remote.describe();
|
|
279
|
+
if (!response.ok) throw new Error(response.error.message ?? "settings describe failed");
|
|
280
|
+
return response.value;
|
|
281
|
+
}
|
|
282
|
+
};
|
|
283
|
+
}
|
|
245
284
|
//#endregion
|
|
246
285
|
//#region ../shared/src/client/styles.ts
|
|
247
286
|
/**
|
|
@@ -265,7 +304,7 @@ window.__ModuleLoader__.load({
|
|
|
265
304
|
.${prefix}-headText{flex-direction:column;flex:1;gap:4px;min-width:0;display:flex}
|
|
266
305
|
.${prefix}-name{color:var(--dsw-alias-label-primary);font-size:15px;font-weight:600;line-height:1.4}
|
|
267
306
|
.${prefix}-description{color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:1.5}
|
|
268
|
-
.${prefix}-chevron{color:var(--dsw-alias-label-tertiary);flex:none;transition:transform .16s;
|
|
307
|
+
.${prefix}-chevron{color:var(--dsw-alias-label-tertiary);flex:none;transition:transform .16s;display:inline-flex}
|
|
269
308
|
.${prefix}-chevronOpen{transform:rotate(180deg)}
|
|
270
309
|
.${prefix}-pending{white-space:nowrap;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary);border-radius:999px;flex:none;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}
|
|
271
310
|
.${prefix}-statusBadge{white-space:nowrap;border-radius:999px;flex:none;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}
|
|
@@ -279,16 +318,24 @@ window.__ModuleLoader__.load({
|
|
|
279
318
|
.${prefix}-badge{white-space:nowrap;border-radius:999px;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}
|
|
280
319
|
.${prefix}-badgeSet{background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary)}
|
|
281
320
|
.${prefix}-badgeUnset{color:var(--dsw-alias-label-tertiary)}
|
|
282
|
-
.${prefix}-input{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);height:34px;font:inherit;color:var(--dsw-alias-label-primary);border-radius:8px;padding:0 12px;font-size:13px}
|
|
321
|
+
.${prefix}-input{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);height:34px;font:inherit;color:var(--dsw-alias-label-primary);border-radius:8px;padding:0 12px;font-size:13px;transition:border-color .18s,background .18s}
|
|
322
|
+
.${prefix}-input:hover:not(:disabled):not(:focus-visible){border-color:var(--dsw-alias-label-dimmed)}
|
|
283
323
|
.${prefix}-input:focus-visible{border-color:var(--dsw-alias-brand-primary);outline:none}
|
|
284
324
|
.${prefix}-input:disabled{color:var(--dsw-alias-label-tertiary);cursor:default}
|
|
285
325
|
.${prefix}-inputInvalid{border-color:var(--dsw-alias-label-error)}
|
|
286
|
-
.${prefix}-select{appearance:none;background-image:linear-gradient(45deg,transparent 50%,var(--dsw-alias-label-
|
|
326
|
+
.${prefix}-select{appearance:none;background:var(--dsw-alias-bg-module-platform);border:none;cursor:pointer;background-image:linear-gradient(45deg,transparent 50%,var(--dsw-alias-label-secondary) 50%),linear-gradient(135deg,var(--dsw-alias-label-secondary) 50%,transparent 50%);background-position:calc(100% - 16px) 50%,calc(100% - 11px) 50%;background-size:5px 5px;background-repeat:no-repeat;color:var(--dsw-alias-label-primary);font:inherit;font-size:13px;height:34px;box-sizing:border-box;border-radius:8px;padding:0 30px 0 12px;transition:background-color .18s}
|
|
327
|
+
.${prefix}-select:hover:not(:disabled){background-color:var(--dsw-alias-interactive-bg-hover)}
|
|
287
328
|
.${prefix}-hint{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:1.5}
|
|
288
329
|
.${prefix}-invalid{color:var(--dsw-alias-label-error);margin:0;font-size:12px;line-height:1.5}
|
|
289
|
-
.${prefix}-checkRow{align-items:center;gap:
|
|
290
|
-
.${prefix}-checkRow
|
|
291
|
-
|
|
330
|
+
.${prefix}-checkRow{align-items:center;gap:10px;display:flex;padding:6px 0}
|
|
331
|
+
.${prefix}-checkRow label{color:var(--dsw-alias-label-primary);font-size:13px;line-height:1.5;cursor:pointer;flex:1;min-width:0}
|
|
332
|
+
/* 自绘圆角扁平开关(官方 UI 无可见原生 checkbox,选择类全走胶囊/菜单) */
|
|
333
|
+
.${prefix}-checkRow input{appearance:none;-webkit-appearance:none;position:relative;box-sizing:border-box;background:var(--dsw-alias-bg-module-platform);border:none;border-radius:999px;width:34px;height:20px;flex:none;cursor:pointer;transition:background .18s;margin:0}
|
|
334
|
+
.${prefix}-checkRow input::after{content:"";position:absolute;top:2px;left:2px;background:var(--dsw-alias-label-primary);border-radius:999px;width:16px;height:16px;transition:left .18s cubic-bezier(.4,0,.2,1)}
|
|
335
|
+
.${prefix}-checkRow input:checked{background:var(--dsw-alias-brand-primary)}
|
|
336
|
+
.${prefix}-checkRow input:checked::after{left:16px;background:var(--dsw-alias-bg-base)}
|
|
337
|
+
.${prefix}-checkRow input:disabled{opacity:.4;cursor:default}
|
|
338
|
+
.${prefix}-checkRow input:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px}
|
|
292
339
|
.${prefix}-groupLabel{color:var(--dsw-alias-label-secondary);font-size:12px;font-weight:600;padding:12px 0 2px;margin:0}
|
|
293
340
|
.${prefix}-readOnly{color:var(--dsw-alias-label-tertiary);margin:12px 0 0;font-size:12px;line-height:1.5}
|
|
294
341
|
.${prefix}-warn{color:var(--dsw-alias-label-error);margin:12px 0 0;font-size:12px;line-height:1.5}
|
|
@@ -306,7 +353,6 @@ window.__ModuleLoader__.load({
|
|
|
306
353
|
.${prefix}-header{padding:12px 12px}
|
|
307
354
|
.${prefix}-input,.${prefix}-select{font-size:16px;height:40px}
|
|
308
355
|
.${prefix}-checkRow{min-height:44px;padding:8px 0}
|
|
309
|
-
.${prefix}-checkRow input{width:18px;height:18px;flex:none}
|
|
310
356
|
.${prefix}-footer{position:sticky;bottom:0;background:var(--dsw-alias-bg-layer-2);margin:0 -10px;padding:10px 10px 6px;border-top:1px solid var(--dsw-alias-border-l2);border-bottom-left-radius:12px;border-bottom-right-radius:12px}
|
|
311
357
|
.${prefix}-status{flex-basis:100%;flex:none}
|
|
312
358
|
.${prefix}-btn{min-height:44px;flex:1;min-width:96px}
|
|
@@ -408,10 +454,10 @@ ${extra}
|
|
|
408
454
|
}, [value, draft]);
|
|
409
455
|
(0, react.useEffect)(() => {
|
|
410
456
|
let alive = true;
|
|
411
|
-
api.describe(
|
|
412
|
-
if (!alive
|
|
413
|
-
const
|
|
414
|
-
setTokenConfigured(
|
|
457
|
+
api.describe().then((view) => {
|
|
458
|
+
if (!alive) return;
|
|
459
|
+
const ns = view.namespaces.find((candidate) => candidate.ns === SETTINGS_NS);
|
|
460
|
+
setTokenConfigured(ns?.secrets.some((secret) => secret.path.join(".") === "token" && secret.set) ?? false);
|
|
415
461
|
}).catch(() => {});
|
|
416
462
|
return () => {
|
|
417
463
|
alive = false;
|
|
@@ -448,12 +494,7 @@ ${extra}
|
|
|
448
494
|
const patch = toPatch(draft);
|
|
449
495
|
if (draft.token !== "") patch.token = draft.token;
|
|
450
496
|
const revision = scope.getSnapshot().revision;
|
|
451
|
-
api.update({
|
|
452
|
-
ns: SETTINGS_NS,
|
|
453
|
-
patch,
|
|
454
|
-
...revision !== void 0 ? { expectedRevision: revision } : {}
|
|
455
|
-
}).then(async (response) => {
|
|
456
|
-
if (!response.result.ok) throw new Error(response.result.error?.message ?? t("saveFailed"));
|
|
497
|
+
api.update(patch, revision).then(async () => {
|
|
457
498
|
await scope.load();
|
|
458
499
|
const next = scope.getSnapshot().value;
|
|
459
500
|
if (next !== void 0) setDraft(draftFromValue(next));
|
|
@@ -757,8 +798,8 @@ ${extra}
|
|
|
757
798
|
const inject = [
|
|
758
799
|
"slots",
|
|
759
800
|
"locale",
|
|
760
|
-
"
|
|
761
|
-
"remote"
|
|
801
|
+
"remote",
|
|
802
|
+
"remote.settings"
|
|
762
803
|
];
|
|
763
804
|
function apply(ctx) {
|
|
764
805
|
const c = ctx;
|
|
@@ -770,8 +811,8 @@ ${extra}
|
|
|
770
811
|
zh,
|
|
771
812
|
en
|
|
772
813
|
}), "access-gate: locale");
|
|
773
|
-
const
|
|
774
|
-
const
|
|
814
|
+
const scope = createSettingsScope(c, SETTINGS_NS, "access-gate: settings scope");
|
|
815
|
+
const api = createNamespaceApi(c.get("remote").settings, SETTINGS_NS);
|
|
775
816
|
c.slots.inject("settings.plugin.item", () => c.slots.register({
|
|
776
817
|
name: "settings.plugin.item",
|
|
777
818
|
key: SETTINGS_NS,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dsh-plus/access-gate",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "dsh-plus service+ui plugin: dsh web 全量访问围栏——本机直连永久放行,远程按白名单 IP(XFF 还原)或 token(登录页 + cookie)放行",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -23,28 +23,27 @@
|
|
|
23
23
|
"dsh": {
|
|
24
24
|
"client": {
|
|
25
25
|
"inject": [
|
|
26
|
-
"@deepseek-ai/dsh-client-runtime",
|
|
27
26
|
"@deepseek-ai/dsh-client-locale",
|
|
28
|
-
"@deepseek-ai/dsh-client-
|
|
27
|
+
"@deepseek-ai/dsh-client-ui-renderer",
|
|
29
28
|
"@deepseek-ai/dsh-api-remotes"
|
|
30
29
|
],
|
|
31
30
|
"platform": "web"
|
|
32
31
|
}
|
|
33
32
|
},
|
|
34
33
|
"dependencies": {
|
|
35
|
-
"@dsh-plus/shared": "0.1.
|
|
34
|
+
"@dsh-plus/shared": "0.1.6"
|
|
36
35
|
},
|
|
37
36
|
"peerDependencies": {
|
|
38
37
|
"@deepseek-ai/cordis": "^4.0.1",
|
|
39
|
-
"@deepseek-ai/dsh-home-paths": "^0.1.
|
|
40
|
-
"@deepseek-ai/dsh-settings": "^0.1.
|
|
38
|
+
"@deepseek-ai/dsh-home-paths": "^0.1.2-alpha.1",
|
|
39
|
+
"@deepseek-ai/dsh-settings": "^0.1.2-alpha.1",
|
|
41
40
|
"@deepseek-ai/schemastery": "^3.18.1"
|
|
42
41
|
},
|
|
43
42
|
"devDependencies": {
|
|
44
43
|
"@deepseek-ai/cordis": "4.0.1",
|
|
45
|
-
"@deepseek-ai/dsh-home-paths": "0.1.
|
|
46
|
-
"@deepseek-ai/dsh-settings": "0.1.
|
|
47
|
-
"@deepseek-ai/dsh-host-webserver": "0.1.
|
|
44
|
+
"@deepseek-ai/dsh-home-paths": "0.1.2-alpha.1",
|
|
45
|
+
"@deepseek-ai/dsh-settings": "0.1.2-alpha.1",
|
|
46
|
+
"@deepseek-ai/dsh-host-webserver": "0.1.2-alpha.1",
|
|
48
47
|
"@deepseek-ai/schemastery": "3.18.1",
|
|
49
48
|
"@types/react": "~18.3.1",
|
|
50
49
|
"react": "^18.2.0"
|
package/src/client/card.tsx
CHANGED
|
@@ -14,8 +14,8 @@ import {
|
|
|
14
14
|
type CardStatusState,
|
|
15
15
|
CheckRow,
|
|
16
16
|
IDLE_STATUS,
|
|
17
|
+
type NamespaceSettingsApi,
|
|
17
18
|
type Scope,
|
|
18
|
-
type SettingsApi,
|
|
19
19
|
TextField,
|
|
20
20
|
} from '@dsh-plus/shared/client'
|
|
21
21
|
import { type ReactElement, useEffect, useMemo, useState, useSyncExternalStore } from 'react'
|
|
@@ -24,7 +24,7 @@ import { SETTINGS_NS } from '../ns.ts'
|
|
|
24
24
|
export interface CardProps {
|
|
25
25
|
t(key: string): string
|
|
26
26
|
scope: Scope
|
|
27
|
-
api:
|
|
27
|
+
api: NamespaceSettingsApi
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
/** settings 命名空间的脱敏解析值(token 被脱敏剥除)。 */
|
|
@@ -126,12 +126,12 @@ export function AccessGateCard(props: CardProps): ReactElement | null {
|
|
|
126
126
|
useEffect(() => {
|
|
127
127
|
let alive = true
|
|
128
128
|
api
|
|
129
|
-
.describe(
|
|
130
|
-
.then((
|
|
131
|
-
if (!alive
|
|
132
|
-
const
|
|
129
|
+
.describe()
|
|
130
|
+
.then((view) => {
|
|
131
|
+
if (!alive) return
|
|
132
|
+
const ns = view.namespaces.find((candidate) => candidate.ns === SETTINGS_NS)
|
|
133
133
|
setTokenConfigured(
|
|
134
|
-
|
|
134
|
+
ns?.secrets.some((secret) => secret.path.join('.') === 'token' && secret.set) ?? false,
|
|
135
135
|
)
|
|
136
136
|
})
|
|
137
137
|
.catch(() => {})
|
|
@@ -192,15 +192,8 @@ export function AccessGateCard(props: CardProps): ReactElement | null {
|
|
|
192
192
|
if (draft.token !== '') patch.token = draft.token
|
|
193
193
|
const revision = scope.getSnapshot().revision
|
|
194
194
|
api
|
|
195
|
-
.update(
|
|
196
|
-
|
|
197
|
-
patch,
|
|
198
|
-
...(revision !== undefined ? { expectedRevision: revision } : {}),
|
|
199
|
-
})
|
|
200
|
-
.then(async (response) => {
|
|
201
|
-
if (!response.result.ok) {
|
|
202
|
-
throw new Error(response.result.error?.message ?? t('saveFailed'))
|
|
203
|
-
}
|
|
195
|
+
.update(patch, revision)
|
|
196
|
+
.then(async () => {
|
|
204
197
|
await scope.load()
|
|
205
198
|
const next = scope.getSnapshot().value as ConfigValue | undefined
|
|
206
199
|
if (next !== undefined) setDraft(draftFromValue(next))
|
package/src/client/client.ts
CHANGED
|
@@ -3,17 +3,21 @@
|
|
|
3
3
|
* 构建产物为 window.__ModuleLoader__.load({id, factory}) 形式的 CJS factory
|
|
4
4
|
* (包装见 tsdown.config.ts);样式沿用官方 data-plugin-css 约定,HMR 据此卸载。
|
|
5
5
|
*
|
|
6
|
-
* 类型说明:浏览器半只用到 slots/locale/
|
|
6
|
+
* 类型说明:浏览器半只用到 slots/locale/remote 的很窄一面,
|
|
7
7
|
* scope 与基础控件走 @dsh-plus/shared/client 套件(收编自本插件原实现)。
|
|
8
|
-
*
|
|
8
|
+
* 该插槽为 keyed 槽位:key 必须是卡片编辑的 settings 命名空间
|
|
9
9
|
* (即服务端 settingsNamespace() 注册的同一字面量,见 ../ns.ts),
|
|
10
10
|
* 官方配置页按此 key 与 Host 已注册命名空间配对分发。
|
|
11
|
-
*
|
|
12
|
-
*
|
|
11
|
+
* 配置读写经 ctx.remote.settings 直连(0.1.2-alpha.1 起 connection.api.settings
|
|
12
|
+
* 已移除;不复用 settingsScope 服务——非 loopback 页面下它固定 memory 模式无数据)。
|
|
13
13
|
* @module @dsh-plus/access-gate/client
|
|
14
14
|
*/
|
|
15
15
|
import type { Context } from '@deepseek-ai/cordis'
|
|
16
|
-
import {
|
|
16
|
+
import {
|
|
17
|
+
createNamespaceApi,
|
|
18
|
+
createSettingsScope,
|
|
19
|
+
type SettingsRemoteFace,
|
|
20
|
+
} from '@dsh-plus/shared/client'
|
|
17
21
|
import { SETTINGS_NS } from '../ns.ts'
|
|
18
22
|
import { AccessGateCard } from './card.tsx'
|
|
19
23
|
import { en, NS, zh } from './i18n.ts'
|
|
@@ -22,7 +26,7 @@ import { injectStyle } from './styles.ts'
|
|
|
22
26
|
export const name = 'dsh-plus-access-gate'
|
|
23
27
|
|
|
24
28
|
/** 浏览器半需要的 cordis 服务 key(loader 据此注入;package.json 的 dsh.client.inject 管包加载顺序)。 */
|
|
25
|
-
export const inject = ['slots', 'locale', '
|
|
29
|
+
export const inject = ['slots', 'locale', 'remote', 'remote.settings'] as const
|
|
26
30
|
|
|
27
31
|
interface SlotsLike {
|
|
28
32
|
inject(key: string, callback: () => unknown): unknown
|
|
@@ -35,17 +39,13 @@ interface LocaleLike {
|
|
|
35
39
|
}
|
|
36
40
|
|
|
37
41
|
interface RemoteLike {
|
|
42
|
+
settings: SettingsRemoteFace
|
|
38
43
|
$on(event: string, listener: (payload?: unknown) => void): () => void
|
|
39
44
|
}
|
|
40
45
|
|
|
41
|
-
interface ConnectionLike {
|
|
42
|
-
api: { settings: Parameters<typeof createApiScope>[0] }
|
|
43
|
-
}
|
|
44
|
-
|
|
45
46
|
interface ClientContext {
|
|
46
47
|
slots: SlotsLike
|
|
47
48
|
locale: LocaleLike
|
|
48
|
-
get(key: 'connection'): ConnectionLike
|
|
49
49
|
get(key: 'remote'): RemoteLike
|
|
50
50
|
on(event: string, listener: () => void): () => void
|
|
51
51
|
effect(execute: () => () => void, label?: string): unknown
|
|
@@ -61,8 +61,8 @@ export function apply(ctx: Context): void {
|
|
|
61
61
|
'access-gate: style',
|
|
62
62
|
)
|
|
63
63
|
c.effect(() => c.locale.register(NS, { zh, en }), 'access-gate: locale')
|
|
64
|
-
const
|
|
65
|
-
const
|
|
64
|
+
const scope = createSettingsScope(c, SETTINGS_NS, 'access-gate: settings scope')
|
|
65
|
+
const api = createNamespaceApi(c.get('remote').settings, SETTINGS_NS)
|
|
66
66
|
c.slots.inject('settings.plugin.item', () =>
|
|
67
67
|
c.slots.register(
|
|
68
68
|
{
|