@linxin666/dsh-client-ui-community-plugins 0.1.18
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/LICENSE +29 -0
- package/README.i18n.yaml +6 -0
- package/README.md +43 -0
- package/README.zh.md +43 -0
- package/cordis.patch.yml +13 -0
- package/lib/client.js +920 -0
- package/lib/client.js.map +1 -0
- package/lib/index.js +29 -0
- package/package.json +67 -0
package/lib/client.js
ADDED
|
@@ -0,0 +1,920 @@
|
|
|
1
|
+
window.__ModuleLoader__.load({
|
|
2
|
+
id: "@linxin666/dsh-client-ui-community-plugins",
|
|
3
|
+
factory: (require) => {
|
|
4
|
+
var module = { exports: {} };
|
|
5
|
+
var exports = module.exports;
|
|
6
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
7
|
+
let react = require("react");
|
|
8
|
+
let react_jsx_runtime = require("react/jsx-runtime");
|
|
9
|
+
let _deepseek_ai_dsh_client_runtime_client = require("@deepseek-ai/dsh-client-runtime/client");
|
|
10
|
+
//#region \0dsh-css:packages/dsh-community-plugins/src/client/settings-card.module.css.mjs
|
|
11
|
+
const css$1 = ".hX6Abq_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}.hX6Abq_card:hover{border-color:var(--dsw-alias-label-dimmed)}.hX6Abq_cardOpen{background:var(--dsw-alias-bg-layer-2);border-color:var(--dsw-alias-label-dimmed)}.hX6Abq_header{appearance:none;width:100%;font:inherit;color:inherit;text-align:left;cursor:pointer;background:0 0;border:0;border-radius:12px;align-items:center;gap:12px;padding:14px 16px;display:flex}.hX6Abq_header:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:-2px}.hX6Abq_headerStatic{border-radius:12px;align-items:center;gap:12px;width:100%;padding:14px 16px;display:flex}.hX6Abq_headText{flex-direction:column;flex:1;gap:4px;min-width:0;display:flex}.hX6Abq_name{color:var(--dsw-alias-label-primary);font-size:15px;font-weight:600;line-height:1.4}.hX6Abq_description{color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:1.5}.hX6Abq_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}.hX6Abq_chevron{color:var(--dsw-alias-label-tertiary);flex:none;transition:transform .16s}.hX6Abq_chevronOpen{transform:rotate(180deg)}.hX6Abq_body{border-top:1px solid var(--dsw-alias-border-l2);margin:0 16px;padding-bottom:8px}.hX6Abq_readOnly{color:var(--dsw-alias-label-tertiary);margin:12px 0 0;font-size:12px;line-height:1.5}.hX6Abq_notExposed{color:var(--dsw-alias-state-warn-primary);margin:12px 0 0;font-size:12px;line-height:1.5}.hX6Abq_footer{border-top:1px solid var(--dsw-alias-border-l2);justify-content:flex-end;align-items:center;gap:8px;padding:12px 0 4px;display:flex}.hX6Abq_failed{min-width:0;color:var(--dsw-alias-label-error);text-overflow:ellipsis;white-space:nowrap;flex:1;margin:0;font-size:12px;line-height:1.5;overflow:hidden}.hX6Abq_discard,.hX6Abq_save{appearance:none;font:inherit;cursor:pointer;border:1px solid #0000;border-radius:8px;padding:5px 14px;font-size:13px;line-height:1.5}.hX6Abq_discard{border-color:var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);background:0 0}.hX6Abq_discard:hover:not(:disabled){color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-label-dimmed)}.hX6Abq_save{background:var(--dsw-alias-label-primary);color:var(--dsw-alias-bg-layer-3)}.hX6Abq_discard:disabled,.hX6Abq_save:disabled{opacity:.4;cursor:default}.hX6Abq_discard:focus-visible,.hX6Abq_save:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}.hX6Abq_field{flex-direction:column;gap:6px;padding:12px 0;display:flex}.hX6Abq_field+.hX6Abq_field{border-top:1px solid var(--dsw-alias-border-l2)}.hX6Abq_head{align-items:center;gap:8px;display:flex}.hX6Abq_label{min-width:0;color:var(--dsw-alias-label-primary);flex:1;font-size:13px;font-weight:500;line-height:1.5}.hX6Abq_badges{align-items:center;gap:8px;display:inline-flex}.hX6Abq_badge{white-space:nowrap;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary);border-radius:999px;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}.hX6Abq_reset{font:inherit;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;padding:0;font-size:12px;line-height:1.5}.hX6Abq_reset:hover:not(:disabled){color:var(--dsw-alias-label-primary)}.hX6Abq_reset:disabled{cursor:default}.hX6Abq_reset:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px;outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px}.hX6Abq_input,.hX6Abq_select{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;line-height:1.5}.hX6Abq_input:focus-visible,.hX6Abq_select:focus-visible{border-color:var(--dsw-alias-brand-primary);outline:none}.hX6Abq_input:disabled,.hX6Abq_select:disabled{color:var(--dsw-alias-label-tertiary);cursor:default}.hX6Abq_inputInvalid{border:1px solid var(--dsw-alias-label-error);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;line-height:1.5}.hX6Abq_inputInvalid:focus-visible{outline:2px solid var(--dsw-alias-label-error);outline-offset:1px;border-color:var(--dsw-alias-label-error)}.hX6Abq_invalid{color:var(--dsw-alias-label-error);margin:0;font-size:12px;line-height:1.5}.hX6Abq_hint{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:1.5}@media (prefers-reduced-motion:reduce){.hX6Abq_card,.hX6Abq_header,.hX6Abq_chevron,.hX6Abq_chevronOpen,.hX6Abq_discard,.hX6Abq_save{transition:none}}";
|
|
12
|
+
const tagId$1 = "@linxin666/dsh-client-ui-community-plugins/settings-card.module.css";
|
|
13
|
+
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$1) + "]") === null) {
|
|
14
|
+
const tag = document.createElement("style");
|
|
15
|
+
tag.dataset.plugin = "@linxin666/dsh-client-ui-community-plugins";
|
|
16
|
+
tag.dataset.pluginCss = tagId$1;
|
|
17
|
+
tag.textContent = css$1;
|
|
18
|
+
document.head.appendChild(tag);
|
|
19
|
+
}
|
|
20
|
+
var settings_card_module_css_default = {
|
|
21
|
+
"badge": "hX6Abq_badge",
|
|
22
|
+
"badges": "hX6Abq_badges",
|
|
23
|
+
"body": "hX6Abq_body",
|
|
24
|
+
"card": "hX6Abq_card",
|
|
25
|
+
"cardOpen": "hX6Abq_cardOpen",
|
|
26
|
+
"chevron": "hX6Abq_chevron",
|
|
27
|
+
"chevronOpen": "hX6Abq_chevronOpen",
|
|
28
|
+
"description": "hX6Abq_description",
|
|
29
|
+
"discard": "hX6Abq_discard",
|
|
30
|
+
"failed": "hX6Abq_failed",
|
|
31
|
+
"field": "hX6Abq_field",
|
|
32
|
+
"footer": "hX6Abq_footer",
|
|
33
|
+
"head": "hX6Abq_head",
|
|
34
|
+
"headText": "hX6Abq_headText",
|
|
35
|
+
"header": "hX6Abq_header",
|
|
36
|
+
"headerStatic": "hX6Abq_headerStatic",
|
|
37
|
+
"hint": "hX6Abq_hint",
|
|
38
|
+
"input": "hX6Abq_input",
|
|
39
|
+
"inputInvalid": "hX6Abq_inputInvalid",
|
|
40
|
+
"invalid": "hX6Abq_invalid",
|
|
41
|
+
"label": "hX6Abq_label",
|
|
42
|
+
"name": "hX6Abq_name",
|
|
43
|
+
"notExposed": "hX6Abq_notExposed",
|
|
44
|
+
"pending": "hX6Abq_pending",
|
|
45
|
+
"readOnly": "hX6Abq_readOnly",
|
|
46
|
+
"reset": "hX6Abq_reset",
|
|
47
|
+
"save": "hX6Abq_save",
|
|
48
|
+
"select": "hX6Abq_select"
|
|
49
|
+
};
|
|
50
|
+
//#endregion
|
|
51
|
+
//#region src/client/PluginSettingsCard.tsx
|
|
52
|
+
/**
|
|
53
|
+
* Family-shared chrome for plugin settings cards: a disclosure header naming
|
|
54
|
+
* the plugin and what its settings govern, the controls inside, and the save
|
|
55
|
+
* that writes them. Renders nothing while the namespace is unavailable — a
|
|
56
|
+
* deployment that does not compose the owning plugin should show no trace of
|
|
57
|
+
* it. Inlined into each consumer's client bundle; mirrors the official
|
|
58
|
+
* ui-plugin-config PluginCard in a self-contained slice.
|
|
59
|
+
*/
|
|
60
|
+
/**
|
|
61
|
+
* Render one plugin settings card.
|
|
62
|
+
* @param props - the plugin's copy keys, its form state, and its controls.
|
|
63
|
+
* @returns the card, or nothing while the namespace is still loading.
|
|
64
|
+
*/
|
|
65
|
+
function PluginSettingsCard(props) {
|
|
66
|
+
const [open, setOpen] = (0, react.useState)(props.defaultOpen ?? true);
|
|
67
|
+
const { state, alwaysOpen } = props;
|
|
68
|
+
if (!state.available) return null;
|
|
69
|
+
const title = props.t(props.titleKey);
|
|
70
|
+
const description = props.t(props.descriptionKey);
|
|
71
|
+
const blocked = !state.dirty || state.invalid || state.saving;
|
|
72
|
+
const expanded = alwaysOpen === true || open;
|
|
73
|
+
const cardClass = expanded ? `${settings_card_module_css_default.cardOpen} ${settings_card_module_css_default.card}` : settings_card_module_css_default.card;
|
|
74
|
+
const header = alwaysOpen === true ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
75
|
+
className: settings_card_module_css_default.headerStatic,
|
|
76
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
77
|
+
className: settings_card_module_css_default.headText,
|
|
78
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
79
|
+
className: settings_card_module_css_default.name,
|
|
80
|
+
title,
|
|
81
|
+
children: title
|
|
82
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
83
|
+
className: settings_card_module_css_default.description,
|
|
84
|
+
title: description,
|
|
85
|
+
children: description
|
|
86
|
+
})]
|
|
87
|
+
}), state.dirty ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
88
|
+
className: settings_card_module_css_default.pending,
|
|
89
|
+
title: props.t("settings.unsaved"),
|
|
90
|
+
children: props.t("settings.unsaved")
|
|
91
|
+
}) : null]
|
|
92
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
93
|
+
type: "button",
|
|
94
|
+
className: settings_card_module_css_default.header,
|
|
95
|
+
"aria-expanded": open,
|
|
96
|
+
"aria-label": `${props.t(open ? "settings.collapse" : "settings.expand")}: ${title}`,
|
|
97
|
+
onClick: () => {
|
|
98
|
+
setOpen(!open);
|
|
99
|
+
},
|
|
100
|
+
children: [
|
|
101
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
102
|
+
className: settings_card_module_css_default.headText,
|
|
103
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
104
|
+
className: settings_card_module_css_default.name,
|
|
105
|
+
title,
|
|
106
|
+
children: title
|
|
107
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
108
|
+
className: settings_card_module_css_default.description,
|
|
109
|
+
title: description,
|
|
110
|
+
children: description
|
|
111
|
+
})]
|
|
112
|
+
}),
|
|
113
|
+
state.dirty ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
114
|
+
className: settings_card_module_css_default.pending,
|
|
115
|
+
title: props.t("settings.unsaved"),
|
|
116
|
+
children: props.t("settings.unsaved")
|
|
117
|
+
}) : null,
|
|
118
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
|
|
119
|
+
width: "14",
|
|
120
|
+
height: "14",
|
|
121
|
+
viewBox: "0 0 14 14",
|
|
122
|
+
fill: "none",
|
|
123
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
124
|
+
className: open ? `${settings_card_module_css_default.chevron} ${settings_card_module_css_default.chevronOpen}` : settings_card_module_css_default.chevron,
|
|
125
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
126
|
+
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",
|
|
127
|
+
fill: "currentColor"
|
|
128
|
+
})
|
|
129
|
+
})
|
|
130
|
+
]
|
|
131
|
+
});
|
|
132
|
+
if (!state.exposed) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
|
|
133
|
+
className: cardClass,
|
|
134
|
+
children: [header, expanded ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
135
|
+
className: settings_card_module_css_default.body,
|
|
136
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
137
|
+
className: settings_card_module_css_default.notExposed,
|
|
138
|
+
role: "status",
|
|
139
|
+
children: props.t("settings.notExposed")
|
|
140
|
+
})
|
|
141
|
+
}) : null]
|
|
142
|
+
});
|
|
143
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
|
|
144
|
+
className: cardClass,
|
|
145
|
+
children: [header, expanded ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
146
|
+
className: settings_card_module_css_default.body,
|
|
147
|
+
children: [
|
|
148
|
+
!state.writable ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
149
|
+
className: settings_card_module_css_default.readOnly,
|
|
150
|
+
role: "status",
|
|
151
|
+
children: props.t("settings.readOnly")
|
|
152
|
+
}) : null,
|
|
153
|
+
props.children,
|
|
154
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
155
|
+
className: settings_card_module_css_default.footer,
|
|
156
|
+
children: [
|
|
157
|
+
state.failed ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
158
|
+
className: settings_card_module_css_default.failed,
|
|
159
|
+
role: "status",
|
|
160
|
+
children: [props.t("settings.saveFailed"), state.failedReason ? " - " + state.failedReason : ""]
|
|
161
|
+
}) : null,
|
|
162
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
163
|
+
type: "button",
|
|
164
|
+
className: settings_card_module_css_default.discard,
|
|
165
|
+
disabled: !state.dirty || state.saving,
|
|
166
|
+
onClick: props.onDiscard,
|
|
167
|
+
children: props.t("settings.discard")
|
|
168
|
+
}),
|
|
169
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
170
|
+
type: "button",
|
|
171
|
+
className: settings_card_module_css_default.save,
|
|
172
|
+
disabled: blocked,
|
|
173
|
+
onClick: props.onSave,
|
|
174
|
+
children: props.t(!state.saving ? "settings.save" : "settings.saving")
|
|
175
|
+
})
|
|
176
|
+
]
|
|
177
|
+
})
|
|
178
|
+
]
|
|
179
|
+
}) : null]
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
/** A staged boolean field: 继承 / 开 / 关. */
|
|
183
|
+
function BooleanField(props) {
|
|
184
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
185
|
+
className: settings_card_module_css_default.field,
|
|
186
|
+
children: [
|
|
187
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
188
|
+
className: settings_card_module_css_default.head,
|
|
189
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
|
|
190
|
+
className: settings_card_module_css_default.label,
|
|
191
|
+
htmlFor: props.id,
|
|
192
|
+
children: props.label
|
|
193
|
+
}), props.overridden ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
194
|
+
className: settings_card_module_css_default.badges,
|
|
195
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
196
|
+
className: settings_card_module_css_default.badge,
|
|
197
|
+
children: props.overriddenLabel
|
|
198
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
199
|
+
type: "button",
|
|
200
|
+
className: settings_card_module_css_default.reset,
|
|
201
|
+
disabled: props.disabled,
|
|
202
|
+
onClick: props.onReset,
|
|
203
|
+
children: props.resetLabel
|
|
204
|
+
})]
|
|
205
|
+
}) : null]
|
|
206
|
+
}),
|
|
207
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
208
|
+
id: props.id,
|
|
209
|
+
className: settings_card_module_css_default.select,
|
|
210
|
+
value: props.text,
|
|
211
|
+
disabled: props.disabled,
|
|
212
|
+
onChange: (event) => {
|
|
213
|
+
props.onEdit(event.target.value);
|
|
214
|
+
},
|
|
215
|
+
children: [
|
|
216
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
217
|
+
value: "",
|
|
218
|
+
children: props.inheritLabel
|
|
219
|
+
}),
|
|
220
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
221
|
+
value: "true",
|
|
222
|
+
children: props.onLabel
|
|
223
|
+
}),
|
|
224
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
225
|
+
value: "false",
|
|
226
|
+
children: props.offLabel
|
|
227
|
+
})
|
|
228
|
+
]
|
|
229
|
+
}),
|
|
230
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
231
|
+
className: settings_card_module_css_default.hint,
|
|
232
|
+
children: props.hint
|
|
233
|
+
})
|
|
234
|
+
]
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
//#endregion
|
|
238
|
+
//#region src/client/settings-form.ts
|
|
239
|
+
/** A boolean field, edited through true/false draft text. */
|
|
240
|
+
function booleanField(field) {
|
|
241
|
+
return {
|
|
242
|
+
field,
|
|
243
|
+
format: (value) => typeof value === "boolean" ? String(value) : "",
|
|
244
|
+
parse: (text) => {
|
|
245
|
+
const trimmed = text.trim();
|
|
246
|
+
if (trimmed === "") return { kind: "clear" };
|
|
247
|
+
if (trimmed === "true") return {
|
|
248
|
+
kind: "set",
|
|
249
|
+
value: true
|
|
250
|
+
};
|
|
251
|
+
if (trimmed === "false") return {
|
|
252
|
+
kind: "set",
|
|
253
|
+
value: false
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Stages one card's edits over one settings namespace and writes them on save.
|
|
260
|
+
*
|
|
261
|
+
* The Host is the only authority on whether a value was accepted — its
|
|
262
|
+
* validators own the constraints no schema can express — so the outcome is
|
|
263
|
+
* read back from the section rather than predicted here. A save that did not
|
|
264
|
+
* land keeps its drafts, so the user can correct them instead of retyping.
|
|
265
|
+
*/
|
|
266
|
+
var CardForm = class {
|
|
267
|
+
scope;
|
|
268
|
+
specs;
|
|
269
|
+
staged = /* @__PURE__ */ new Map();
|
|
270
|
+
listeners = /* @__PURE__ */ new Set();
|
|
271
|
+
saving = false;
|
|
272
|
+
failed = false;
|
|
273
|
+
failedReason;
|
|
274
|
+
/** @param scope - the bound settings scope for this card's namespace. */
|
|
275
|
+
constructor(scope, specs) {
|
|
276
|
+
this.scope = scope;
|
|
277
|
+
this.specs = new Map(specs.map((spec) => [spec.field, spec]));
|
|
278
|
+
scope.subscribe(() => {
|
|
279
|
+
this.publish();
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
/** Publish a projection of this form, rebuilt whenever the scope or a draft changes. */
|
|
283
|
+
bind(project) {
|
|
284
|
+
const store = (0, _deepseek_ai_dsh_client_runtime_client.createSnapshotStore)(project());
|
|
285
|
+
this.listeners.add(() => {
|
|
286
|
+
store.set(project());
|
|
287
|
+
});
|
|
288
|
+
return store;
|
|
289
|
+
}
|
|
290
|
+
/** Read the card-level state: what the Host serves, and what a save would do. */
|
|
291
|
+
shell() {
|
|
292
|
+
const snapshot = this.scope.getSnapshot();
|
|
293
|
+
const plan = this.plan();
|
|
294
|
+
return {
|
|
295
|
+
available: snapshot.status !== "loading",
|
|
296
|
+
exposed: snapshot.status === "ready",
|
|
297
|
+
writable: snapshot.writable,
|
|
298
|
+
dirty: plan.length > 0,
|
|
299
|
+
invalid: plan.some((item) => item.run === void 0),
|
|
300
|
+
saving: this.saving,
|
|
301
|
+
failed: this.failed,
|
|
302
|
+
...this.failedReason === void 0 ? {} : { failedReason: this.failedReason }
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
/** Read one field's state from the effective section and its staged draft. */
|
|
306
|
+
field(field) {
|
|
307
|
+
const spec = this.specOf(field);
|
|
308
|
+
const staged = this.staged.get(field);
|
|
309
|
+
if (staged === void 0) return {
|
|
310
|
+
text: spec.format(this.sectionValue(field)),
|
|
311
|
+
overridden: this.stored(field),
|
|
312
|
+
invalid: false
|
|
313
|
+
};
|
|
314
|
+
const write = staged.clear ? { kind: "clear" } : spec.parse(staged.text);
|
|
315
|
+
return {
|
|
316
|
+
text: staged.text,
|
|
317
|
+
overridden: write?.kind === "set",
|
|
318
|
+
invalid: write === void 0
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
/** The actions the card's slot registration injects. */
|
|
322
|
+
actions() {
|
|
323
|
+
return {
|
|
324
|
+
edit: (field, text) => {
|
|
325
|
+
this.stage(field, {
|
|
326
|
+
text,
|
|
327
|
+
clear: false
|
|
328
|
+
});
|
|
329
|
+
},
|
|
330
|
+
resetField: (field) => {
|
|
331
|
+
this.stage(field, {
|
|
332
|
+
text: this.specOf(field).format(this.baseValue(field)),
|
|
333
|
+
clear: true
|
|
334
|
+
});
|
|
335
|
+
},
|
|
336
|
+
save: () => {
|
|
337
|
+
this.save();
|
|
338
|
+
},
|
|
339
|
+
discard: () => {
|
|
340
|
+
if (this.staged.size === 0 && !this.failed) return;
|
|
341
|
+
this.staged.clear();
|
|
342
|
+
this.failed = false;
|
|
343
|
+
this.failedReason = void 0;
|
|
344
|
+
this.publish();
|
|
345
|
+
}
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
/**
|
|
349
|
+
* Write every staged edit, then re-seed from what the Host accepted.
|
|
350
|
+
*
|
|
351
|
+
* When the scope carries the optional batch surface (the dsh-web-ui
|
|
352
|
+
* bridge scope), every planned write rides one mutation so cross-field
|
|
353
|
+
* validate hooks (baseURL+model) judge the batch as a unit instead of
|
|
354
|
+
* deadlocking on per-field writes. Otherwise the per-field loop runs.
|
|
355
|
+
* A field lands only when the Host reports it held the staged value; a
|
|
356
|
+
* landed field's draft is dropped, a failed one stays staged for the user.
|
|
357
|
+
* @returns settlement after every write and the read-back.
|
|
358
|
+
*/
|
|
359
|
+
async save() {
|
|
360
|
+
const plan = this.plan();
|
|
361
|
+
const valid = plan.filter((item) => item.run !== void 0);
|
|
362
|
+
if (plan.length === 0 || this.saving || valid.length !== plan.length) return;
|
|
363
|
+
const plannedWrites = valid.map((item) => item.op);
|
|
364
|
+
const fields = new Set(plan.map((item) => item.field));
|
|
365
|
+
this.saving = true;
|
|
366
|
+
this.failed = false;
|
|
367
|
+
this.failedReason = void 0;
|
|
368
|
+
this.publish();
|
|
369
|
+
const landed = /* @__PURE__ */ new Set();
|
|
370
|
+
const batch = this.batchedScope();
|
|
371
|
+
if (batch !== void 0) {
|
|
372
|
+
const result = await batch.mutate(plannedWrites);
|
|
373
|
+
if (result.ok) {
|
|
374
|
+
for (const field of result.fields) if (field.landed) landed.add(field.field);
|
|
375
|
+
} else this.failedReason = result.message;
|
|
376
|
+
} else for (const item of valid) if (await item.run()) landed.add(item.field);
|
|
377
|
+
for (const field of fields) if (landed.has(field)) this.staged.delete(field);
|
|
378
|
+
this.saving = false;
|
|
379
|
+
this.failed = landed.size !== fields.size;
|
|
380
|
+
this.publish();
|
|
381
|
+
}
|
|
382
|
+
/** The scope's batch surface when it supports one; undefined conservatively otherwise. */
|
|
383
|
+
batchedScope() {
|
|
384
|
+
const candidate = this.scope;
|
|
385
|
+
return typeof candidate?.mutate === "function" ? candidate : void 0;
|
|
386
|
+
}
|
|
387
|
+
/**
|
|
388
|
+
* Every staged edit a save would write. An entry whose draft is not a value
|
|
389
|
+
* its field accepts carries no write: the form is still dirty, and the save
|
|
390
|
+
* refuses rather than dropping the edit. A staged edit that matches the
|
|
391
|
+
* effective section is not a write at all.
|
|
392
|
+
* @returns the planned writes, in the order the fields were staged.
|
|
393
|
+
*/
|
|
394
|
+
plan() {
|
|
395
|
+
const plan = [];
|
|
396
|
+
for (const [field, staged] of this.staged) {
|
|
397
|
+
const spec = this.specOf(field);
|
|
398
|
+
if (staged.clear) {
|
|
399
|
+
if (this.stored(field)) plan.push({
|
|
400
|
+
field,
|
|
401
|
+
op: {
|
|
402
|
+
field,
|
|
403
|
+
op: "unset"
|
|
404
|
+
},
|
|
405
|
+
run: () => this.clear(field)
|
|
406
|
+
});
|
|
407
|
+
continue;
|
|
408
|
+
}
|
|
409
|
+
if (staged.text === spec.format(this.sectionValue(field))) continue;
|
|
410
|
+
const write = spec.parse(staged.text);
|
|
411
|
+
if (write === void 0) plan.push({
|
|
412
|
+
field,
|
|
413
|
+
op: {
|
|
414
|
+
field,
|
|
415
|
+
op: "unset"
|
|
416
|
+
},
|
|
417
|
+
run: void 0
|
|
418
|
+
});
|
|
419
|
+
else if (write.kind === "clear") plan.push({
|
|
420
|
+
field,
|
|
421
|
+
op: {
|
|
422
|
+
field,
|
|
423
|
+
op: "unset"
|
|
424
|
+
},
|
|
425
|
+
run: () => this.clear(field)
|
|
426
|
+
});
|
|
427
|
+
else plan.push({
|
|
428
|
+
field,
|
|
429
|
+
op: {
|
|
430
|
+
field,
|
|
431
|
+
op: "set",
|
|
432
|
+
value: write.value
|
|
433
|
+
},
|
|
434
|
+
run: () => this.store(field, write.value)
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
return plan;
|
|
438
|
+
}
|
|
439
|
+
async clear(field) {
|
|
440
|
+
await this.scope.unset(field);
|
|
441
|
+
return !this.stored(field);
|
|
442
|
+
}
|
|
443
|
+
async store(field, value) {
|
|
444
|
+
await this.scope.set(field, value);
|
|
445
|
+
if (this.specOf(field).secret) return true;
|
|
446
|
+
return this.userLayer()?.[field] === value;
|
|
447
|
+
}
|
|
448
|
+
stage(field, edit) {
|
|
449
|
+
this.staged.set(field, edit);
|
|
450
|
+
this.failed = false;
|
|
451
|
+
this.failedReason = void 0;
|
|
452
|
+
this.publish();
|
|
453
|
+
}
|
|
454
|
+
specOf(field) {
|
|
455
|
+
const spec = this.specs.get(field);
|
|
456
|
+
if (spec === void 0) throw new Error(`settings card has no field ${field}`);
|
|
457
|
+
return spec;
|
|
458
|
+
}
|
|
459
|
+
snapshotOf() {
|
|
460
|
+
return this.scope.getSnapshot();
|
|
461
|
+
}
|
|
462
|
+
sectionValue(field) {
|
|
463
|
+
return this.snapshotOf().value?.[field];
|
|
464
|
+
}
|
|
465
|
+
baseValue(field) {
|
|
466
|
+
return this.snapshotOf().base?.[field];
|
|
467
|
+
}
|
|
468
|
+
userLayer() {
|
|
469
|
+
return this.snapshotOf().user;
|
|
470
|
+
}
|
|
471
|
+
stored(field) {
|
|
472
|
+
const user = this.userLayer();
|
|
473
|
+
return user !== void 0 && Object.hasOwn(user, field);
|
|
474
|
+
}
|
|
475
|
+
publish() {
|
|
476
|
+
for (const listener of this.listeners) listener();
|
|
477
|
+
}
|
|
478
|
+
};
|
|
479
|
+
//#endregion
|
|
480
|
+
//#region src/client/generated/community.ts
|
|
481
|
+
/** Every community plugin, in community.json order. */
|
|
482
|
+
const COMMUNITY_PLUGINS = [
|
|
483
|
+
{
|
|
484
|
+
"id": "dsh-data-agent",
|
|
485
|
+
"name": "Data Agent",
|
|
486
|
+
"nameEn": "Data Agent",
|
|
487
|
+
"author": "omdsh-dev",
|
|
488
|
+
"repo": "https://github.com/omdsh-dev/dsh-data-agent",
|
|
489
|
+
"description": "为 DSH 定义专用 Data Agent 预设,让 AI 帮你查询、更新、分析数据。",
|
|
490
|
+
"descriptionEn": "Defines a dedicated Data Agent preset for DSH so the AI can query, update and analyze data."
|
|
491
|
+
},
|
|
492
|
+
{
|
|
493
|
+
"id": "dsh-tui",
|
|
494
|
+
"name": "dsh-TUI",
|
|
495
|
+
"nameEn": "dsh-TUI",
|
|
496
|
+
"author": "ccch1mneyyy",
|
|
497
|
+
"repo": "https://github.com/ccch1mneyyy/dsh-TUI",
|
|
498
|
+
"description": "Claude Code 风格全屏交互终端插件:像素鲸鱼顶栏、实时工作状态行、思考流式展开、双击 Esc 回滚、上下文进度条与 TPS 仪表。",
|
|
499
|
+
"descriptionEn": "A Claude Code style fullscreen interactive terminal plugin: pixel-whale header, live working-state line, streaming reasoning expansion, double-Esc rollback, context progress bar and TPS gauges."
|
|
500
|
+
},
|
|
501
|
+
{
|
|
502
|
+
"id": "dsh-tianshu-tui",
|
|
503
|
+
"name": "天书 TUI",
|
|
504
|
+
"nameEn": "Tianshu TUI",
|
|
505
|
+
"author": "huiliyi37",
|
|
506
|
+
"repo": "https://github.com/huiliyi37/dsh-tianshu-tui",
|
|
507
|
+
"description": "基于官方 DeepSeek Harness 的交互式终端 UI 插件,在官方基础上增加 TDD 与证据门等工作流。",
|
|
508
|
+
"descriptionEn": "An interactive terminal UI plugin for DeepSeek Harness that adds TDD and evidence-gate workflows on top of the official base."
|
|
509
|
+
},
|
|
510
|
+
{
|
|
511
|
+
"id": "dsh-chat-summary",
|
|
512
|
+
"name": "Chat Summary",
|
|
513
|
+
"nameEn": "Chat Summary",
|
|
514
|
+
"author": "v833",
|
|
515
|
+
"repo": "https://github.com/v833/dsh-chat-summary",
|
|
516
|
+
"description": "总结当前对话并导出为 Markdown / DOCX / PDF,可选 LLM 智能总结(用户自配 API Key)。",
|
|
517
|
+
"descriptionEn": "Summarize the current conversation and export it as Markdown / DOCX / PDF, with optional LLM summarization using your own API key.",
|
|
518
|
+
"npm": "@linxin666/dsh-client-ui-chat-summary"
|
|
519
|
+
},
|
|
520
|
+
{
|
|
521
|
+
"id": "dsh-builtin-toggles",
|
|
522
|
+
"name": "内置能力检查器",
|
|
523
|
+
"nameEn": "Built-in Capability Inspector",
|
|
524
|
+
"author": "Starfie1d1272",
|
|
525
|
+
"repo": "https://github.com/Starfie1d1272/dsh-builtin-toggles",
|
|
526
|
+
"description": "Evidence-backed 内置 capability Inspector:展示 DSH Web built-in capability 的 provenance、compatibility 与 structural drift;仅对 9 个经过审阅的 UI leaves 提供 fail-closed 开关。",
|
|
527
|
+
"descriptionEn": "Evidence-backed built-in capability Inspector: surfaces provenance, compatibility and structural drift of DSH Web built-ins, with fail-closed toggles for only the nine reviewed UI leaves.",
|
|
528
|
+
"npm": "dsh-builtin-toggles"
|
|
529
|
+
},
|
|
530
|
+
{
|
|
531
|
+
"id": "dsh-pilot",
|
|
532
|
+
"name": "Pilot 浏览器驾驶舱",
|
|
533
|
+
"nameEn": "Pilot Browser Cockpit",
|
|
534
|
+
"author": "guo6x",
|
|
535
|
+
"repo": "https://github.com/guo6x/dsh-pilot",
|
|
536
|
+
"description": "给 agent 一双会开车的手:零依赖 CDP 浏览器操控(8 个 pilot_* 工具:导航/点击/输入/按键/JS/截图)+ Web GUI 可拖拽驾驶舱面板,无需 Playwright、无需 API key。",
|
|
537
|
+
"descriptionEn": "Give your agent hands: zero-dependency CDP browser control (8 pilot_* tools: navigate/click/type/keys/eval/screenshot) plus a draggable cockpit panel in the Web GUI - no Playwright, no API key."
|
|
538
|
+
},
|
|
539
|
+
{
|
|
540
|
+
"id": "dsh-housekeeper",
|
|
541
|
+
"name": "环境管家",
|
|
542
|
+
"nameEn": "Environment Housekeeper",
|
|
543
|
+
"author": "guo6x",
|
|
544
|
+
"repo": "https://github.com/guo6x/dsh-housekeeper",
|
|
545
|
+
"description": "管住 agent 的脏手:工具链台账(node/pnpm/git/gh/ffmpeg 等自动探测)、缓存与临时目录扫描 + 白名单安全一键清理、机器规则 AGENTS.md 查看编辑,全在设置面板完成。",
|
|
546
|
+
"descriptionEn": "Keep your agent's hands clean: toolchain inventory, scratch/cache scan with whitelist-guarded one-click cleanup, and the machine rules file (AGENTS.md) view/edit - all in the settings panel."
|
|
547
|
+
},
|
|
548
|
+
{
|
|
549
|
+
"id": "dsh-deepread",
|
|
550
|
+
"name": "DeepRead 精读助手",
|
|
551
|
+
"nameEn": "DeepRead Assistant",
|
|
552
|
+
"author": "xiehuan123",
|
|
553
|
+
"repo": "https://github.com/xiehuan123/dsh-deepread",
|
|
554
|
+
"description": "五种模式精读插件(quick / deep / map / feynman / book),支持公众号链接与文件输入,导出 md / mm / html,Web UI 提供工具结果卡片与精读面板。",
|
|
555
|
+
"descriptionEn": "A five-mode deep reading plugin (quick / deep / map / feynman / book) for links and files, with md / mm / html exports and Web UI tool-result cards plus a reading panel.",
|
|
556
|
+
"npm": "dsh-deepread"
|
|
557
|
+
},
|
|
558
|
+
{
|
|
559
|
+
"id": "dsh-mnemon",
|
|
560
|
+
"name": "Mnemon 记忆系统",
|
|
561
|
+
"nameEn": "Mnemon Memory",
|
|
562
|
+
"author": "omdsh-dev",
|
|
563
|
+
"repo": "https://github.com/omdsh-dev/dsh-mnemon",
|
|
564
|
+
"description": "与 Mnemon CLI 集成的跨 Agent、本地优先持久记忆插件:用户画像 / 工作记忆 / 项目档案与长期 Memory Spaces,支持导入导出。",
|
|
565
|
+
"descriptionEn": "A cross-agent, local-first persistent memory plugin integrating the Mnemon CLI: profiles, working memory, project documents and long-term Memory Spaces, with import and export.",
|
|
566
|
+
"npm": "dsh-mnemon"
|
|
567
|
+
}
|
|
568
|
+
];
|
|
569
|
+
//#endregion
|
|
570
|
+
//#region src/client/community-guard.ts
|
|
571
|
+
/** True when the value is a well-formed community plugin entry. */
|
|
572
|
+
function isCommunityPluginEntry(value) {
|
|
573
|
+
if (typeof value !== "object" || value === null) return false;
|
|
574
|
+
const entry = value;
|
|
575
|
+
if (typeof entry.id !== "string" || entry.id === "") return false;
|
|
576
|
+
if (typeof entry.name !== "string" || typeof entry.nameEn !== "string") return false;
|
|
577
|
+
if (typeof entry.author !== "string" || entry.author === "") return false;
|
|
578
|
+
if (typeof entry.repo !== "string" || !entry.repo.startsWith("https://")) return false;
|
|
579
|
+
if (entry.description !== void 0 && typeof entry.description !== "string") return false;
|
|
580
|
+
if (entry.descriptionEn !== void 0 && typeof entry.descriptionEn !== "string") return false;
|
|
581
|
+
if (entry.npm !== void 0 && typeof entry.npm !== "string") return false;
|
|
582
|
+
return true;
|
|
583
|
+
}
|
|
584
|
+
//#endregion
|
|
585
|
+
//#region \0dsh-css:packages/dsh-community-plugins/src/client/community.module.css.mjs
|
|
586
|
+
const css = ".z0J_8a_sectionList{margin:0;padding:0;list-style:none}.z0J_8a_entries{flex-direction:column;gap:8px;margin:8px 0 0;padding:0;list-style:none;display:flex}.z0J_8a_entry{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);border-radius:6px;flex-direction:column;gap:4px;padding:10px 12px;display:flex}.z0J_8a_entryHead{justify-content:space-between;align-items:baseline;gap:8px;min-width:0;display:flex}.z0J_8a_entryName{color:var(--dsw-alias-label-primary);text-overflow:ellipsis;white-space:nowrap;font-weight:600;overflow:hidden}.z0J_8a_entryAuthor{color:var(--dsw-alias-label-tertiary);text-overflow:ellipsis;white-space:nowrap;flex:none;font-size:12px;overflow:hidden}.z0J_8a_entryDescription,.z0J_8a_entryDescriptionEn{color:var(--dsw-alias-label-secondary);margin:0;font-size:13px;line-height:1.4}.z0J_8a_entryDescriptionEn{color:var(--dsw-alias-label-tertiary);font-size:12px}.z0J_8a_entryLinks{flex-wrap:wrap;align-items:center;gap:10px;margin-top:2px;display:flex}.z0J_8a_entryLink{color:var(--dsw-alias-link-primary,var(--dsw-alias-button-info-fill));font-size:13px;text-decoration:underline}.z0J_8a_entryLink:hover{text-decoration:none}.z0J_8a_entryNpm{color:var(--dsw-alias-label-tertiary);text-overflow:ellipsis;white-space:nowrap;font-size:12px;overflow:hidden}.z0J_8a_entryInstall{flex-wrap:wrap;align-items:center;gap:8px;margin-top:2px;display:flex}.z0J_8a_entryCommand{min-width:0;color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);overflow-wrap:anywhere;border-radius:4px;flex:1;padding:2px 6px;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:12px}.z0J_8a_copyButton{color:var(--dsw-alias-label-secondary);border:1px solid var(--dsw-alias-border-l2);cursor:pointer;background:0 0;border-radius:4px;flex:none;padding:2px 10px;font-size:12px;line-height:1.5}.z0J_8a_copyButton:hover{background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary)}.z0J_8a_installNote{color:var(--dsw-alias-label-tertiary);margin:8px 2px 0;font-size:12px;line-height:1.4}.z0J_8a_empty{color:var(--dsw-alias-label-tertiary);margin:0;padding:8px 2px;font-size:13px}.z0J_8a_off{color:var(--dsw-alias-label-tertiary);margin:8px 2px 0;font-size:13px}.z0J_8a_notice{color:var(--dsw-alias-label-tertiary);margin:10px 2px 0;font-size:12px;line-height:1.4}";
|
|
587
|
+
const tagId = "@linxin666/dsh-client-ui-community-plugins/community.module.css";
|
|
588
|
+
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
|
|
589
|
+
const tag = document.createElement("style");
|
|
590
|
+
tag.dataset.plugin = "@linxin666/dsh-client-ui-community-plugins";
|
|
591
|
+
tag.dataset.pluginCss = tagId;
|
|
592
|
+
tag.textContent = css;
|
|
593
|
+
document.head.appendChild(tag);
|
|
594
|
+
}
|
|
595
|
+
var community_module_css_default = {
|
|
596
|
+
"copyButton": "z0J_8a_copyButton",
|
|
597
|
+
"empty": "z0J_8a_empty",
|
|
598
|
+
"entries": "z0J_8a_entries",
|
|
599
|
+
"entry": "z0J_8a_entry",
|
|
600
|
+
"entryAuthor": "z0J_8a_entryAuthor",
|
|
601
|
+
"entryCommand": "z0J_8a_entryCommand",
|
|
602
|
+
"entryDescription": "z0J_8a_entryDescription",
|
|
603
|
+
"entryDescriptionEn": "z0J_8a_entryDescriptionEn",
|
|
604
|
+
"entryHead": "z0J_8a_entryHead",
|
|
605
|
+
"entryInstall": "z0J_8a_entryInstall",
|
|
606
|
+
"entryLink": "z0J_8a_entryLink",
|
|
607
|
+
"entryLinks": "z0J_8a_entryLinks",
|
|
608
|
+
"entryName": "z0J_8a_entryName",
|
|
609
|
+
"entryNpm": "z0J_8a_entryNpm",
|
|
610
|
+
"installNote": "z0J_8a_installNote",
|
|
611
|
+
"notice": "z0J_8a_notice",
|
|
612
|
+
"off": "z0J_8a_off",
|
|
613
|
+
"sectionList": "z0J_8a_sectionList"
|
|
614
|
+
};
|
|
615
|
+
//#endregion
|
|
616
|
+
//#region src/client/CommunityPluginsCard.tsx
|
|
617
|
+
/**
|
|
618
|
+
* The community plugin index card: a first-level settings section that is
|
|
619
|
+
* always open (a static header with the index list directly visible). Its own
|
|
620
|
+
* enable switch (backed by the community-plugins settings namespace) gates
|
|
621
|
+
* the entry list; the list itself points at contributors' own repositories —
|
|
622
|
+
* this package only indexes them, it never vendors their code.
|
|
623
|
+
*/
|
|
624
|
+
/** Bridges the community-plugins scope onto the card's staged form. */
|
|
625
|
+
var CommunityPluginsCardController = class {
|
|
626
|
+
form;
|
|
627
|
+
store;
|
|
628
|
+
/** @param scope - the bound settings scope for the community-plugins namespace. */
|
|
629
|
+
constructor(scope) {
|
|
630
|
+
this.form = new CardForm(scope, [booleanField("enabled")]);
|
|
631
|
+
this.store = this.form.bind(() => this.projection());
|
|
632
|
+
}
|
|
633
|
+
projection() {
|
|
634
|
+
return {
|
|
635
|
+
...this.form.shell(),
|
|
636
|
+
enabled: this.form.field("enabled")
|
|
637
|
+
};
|
|
638
|
+
}
|
|
639
|
+
/**
|
|
640
|
+
* Build the face the card's slot registration injects.
|
|
641
|
+
* @returns the card's snapshot and its form actions.
|
|
642
|
+
*/
|
|
643
|
+
inject() {
|
|
644
|
+
return {
|
|
645
|
+
hooks: { communityPluginsCard: this.store },
|
|
646
|
+
...this.form.actions()
|
|
647
|
+
};
|
|
648
|
+
}
|
|
649
|
+
};
|
|
650
|
+
/** The one-line install command for an entry: npm package when published, else the contributor repository URL. */
|
|
651
|
+
function installCommand(entry) {
|
|
652
|
+
return `dsh plugin --profile web add ${entry.npm ?? entry.repo}`;
|
|
653
|
+
}
|
|
654
|
+
/**
|
|
655
|
+
* Render the community plugin index card.
|
|
656
|
+
* @param props - locale copy, the card snapshot, its form actions, and the
|
|
657
|
+
* (default-generated) entry list.
|
|
658
|
+
* @returns the card.
|
|
659
|
+
*/
|
|
660
|
+
function CommunityPluginsCard(props) {
|
|
661
|
+
const { t } = props;
|
|
662
|
+
const state = props.useCommunityPluginsCard((snapshot) => snapshot);
|
|
663
|
+
const plugins = (props.plugins ?? COMMUNITY_PLUGINS).filter(isCommunityPluginEntry);
|
|
664
|
+
const [copiedId, setCopiedId] = (0, react.useState)(null);
|
|
665
|
+
const copyCommand = (id, command) => {
|
|
666
|
+
const mark = () => {
|
|
667
|
+
setCopiedId(id);
|
|
668
|
+
};
|
|
669
|
+
const clipboard = typeof navigator !== "undefined" ? navigator.clipboard : void 0;
|
|
670
|
+
if (clipboard?.writeText !== void 0) {
|
|
671
|
+
clipboard.writeText(command).then(mark, mark);
|
|
672
|
+
return;
|
|
673
|
+
}
|
|
674
|
+
try {
|
|
675
|
+
const area = document.createElement("textarea");
|
|
676
|
+
area.value = command;
|
|
677
|
+
area.setAttribute("readonly", "");
|
|
678
|
+
area.style.position = "fixed";
|
|
679
|
+
area.style.opacity = "0";
|
|
680
|
+
document.body.append(area);
|
|
681
|
+
area.select();
|
|
682
|
+
document.execCommand("copy");
|
|
683
|
+
area.remove();
|
|
684
|
+
} catch {}
|
|
685
|
+
mark();
|
|
686
|
+
};
|
|
687
|
+
const disabled = !state.writable;
|
|
688
|
+
const visible = state.enabled.text !== "false";
|
|
689
|
+
const fieldProps = {
|
|
690
|
+
overriddenLabel: t("settings.overridden"),
|
|
691
|
+
resetLabel: t("settings.reset"),
|
|
692
|
+
invalidLabel: t("settings.invalidNumber"),
|
|
693
|
+
disabled
|
|
694
|
+
};
|
|
695
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(PluginSettingsCard, {
|
|
696
|
+
t,
|
|
697
|
+
titleKey: "settings.title",
|
|
698
|
+
descriptionKey: "settings.description",
|
|
699
|
+
state,
|
|
700
|
+
alwaysOpen: true,
|
|
701
|
+
onSave: props.save,
|
|
702
|
+
onDiscard: props.discard,
|
|
703
|
+
children: [
|
|
704
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(BooleanField, {
|
|
705
|
+
id: "settings-community-enabled",
|
|
706
|
+
label: t("settings.enabled"),
|
|
707
|
+
hint: t("settings.enabledHint"),
|
|
708
|
+
inheritLabel: t("settings.inherit"),
|
|
709
|
+
onLabel: t("settings.on"),
|
|
710
|
+
offLabel: t("settings.off"),
|
|
711
|
+
...fieldProps,
|
|
712
|
+
...state.enabled,
|
|
713
|
+
onEdit: (text) => {
|
|
714
|
+
props.edit("enabled", text);
|
|
715
|
+
},
|
|
716
|
+
onReset: () => {
|
|
717
|
+
props.resetField("enabled");
|
|
718
|
+
}
|
|
719
|
+
}),
|
|
720
|
+
visible ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
|
|
721
|
+
className: community_module_css_default.entries,
|
|
722
|
+
children: plugins.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", {
|
|
723
|
+
className: community_module_css_default.empty,
|
|
724
|
+
role: "status",
|
|
725
|
+
children: t("empty")
|
|
726
|
+
}) : plugins.map((plugin) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
|
|
727
|
+
className: community_module_css_default.entry,
|
|
728
|
+
children: [
|
|
729
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
730
|
+
className: community_module_css_default.entryHead,
|
|
731
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
732
|
+
className: community_module_css_default.entryName,
|
|
733
|
+
title: plugin.name,
|
|
734
|
+
children: plugin.name
|
|
735
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
736
|
+
className: community_module_css_default.entryAuthor,
|
|
737
|
+
title: plugin.author,
|
|
738
|
+
children: [
|
|
739
|
+
t("author"),
|
|
740
|
+
": ",
|
|
741
|
+
plugin.author
|
|
742
|
+
]
|
|
743
|
+
})]
|
|
744
|
+
}),
|
|
745
|
+
plugin.description ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
746
|
+
className: community_module_css_default.entryDescription,
|
|
747
|
+
children: plugin.description
|
|
748
|
+
}) : null,
|
|
749
|
+
plugin.descriptionEn ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
750
|
+
className: community_module_css_default.entryDescriptionEn,
|
|
751
|
+
children: plugin.descriptionEn
|
|
752
|
+
}) : null,
|
|
753
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
754
|
+
className: community_module_css_default.entryLinks,
|
|
755
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
|
|
756
|
+
className: community_module_css_default.entryLink,
|
|
757
|
+
href: plugin.repo,
|
|
758
|
+
target: "_blank",
|
|
759
|
+
rel: "noreferrer",
|
|
760
|
+
children: t("repository")
|
|
761
|
+
}), plugin.npm ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
|
|
762
|
+
className: community_module_css_default.entryNpm,
|
|
763
|
+
children: plugin.npm
|
|
764
|
+
}) : null]
|
|
765
|
+
}),
|
|
766
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
767
|
+
className: community_module_css_default.entryInstall,
|
|
768
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
|
|
769
|
+
className: community_module_css_default.entryCommand,
|
|
770
|
+
children: installCommand(plugin)
|
|
771
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
772
|
+
type: "button",
|
|
773
|
+
className: community_module_css_default.copyButton,
|
|
774
|
+
onClick: () => {
|
|
775
|
+
copyCommand(plugin.id, installCommand(plugin));
|
|
776
|
+
},
|
|
777
|
+
children: copiedId === plugin.id ? t("copied") : t("copy")
|
|
778
|
+
})]
|
|
779
|
+
})
|
|
780
|
+
]
|
|
781
|
+
}, plugin.id))
|
|
782
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
783
|
+
className: community_module_css_default.off,
|
|
784
|
+
role: "status",
|
|
785
|
+
children: t("off")
|
|
786
|
+
}),
|
|
787
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
788
|
+
className: community_module_css_default.installNote,
|
|
789
|
+
role: "note",
|
|
790
|
+
children: t("installHint")
|
|
791
|
+
}),
|
|
792
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
793
|
+
className: community_module_css_default.notice,
|
|
794
|
+
role: "note",
|
|
795
|
+
children: t("notice")
|
|
796
|
+
})
|
|
797
|
+
]
|
|
798
|
+
});
|
|
799
|
+
}
|
|
800
|
+
/** Render the community plugin index as a first-level settings page. */
|
|
801
|
+
function CommunityPluginsSection(props) {
|
|
802
|
+
const { t, useCommunityPluginsCard, save, discard, edit, resetField, plugins } = props;
|
|
803
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
|
|
804
|
+
className: community_module_css_default.sectionList,
|
|
805
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CommunityPluginsCard, {
|
|
806
|
+
t,
|
|
807
|
+
useCommunityPluginsCard,
|
|
808
|
+
save,
|
|
809
|
+
discard,
|
|
810
|
+
edit,
|
|
811
|
+
resetField,
|
|
812
|
+
plugins
|
|
813
|
+
})
|
|
814
|
+
});
|
|
815
|
+
}
|
|
816
|
+
//#endregion
|
|
817
|
+
//#region src/client/locales.ts
|
|
818
|
+
/**
|
|
819
|
+
* The community-plugins locale dictionaries for the index card.
|
|
820
|
+
*/
|
|
821
|
+
/** Simplified Chinese dictionary (the key-set source of truth). */
|
|
822
|
+
const zh = {
|
|
823
|
+
"settings.title": "社区插件",
|
|
824
|
+
"settings.description": "社区贡献者开发与维护的插件,链接指向作者自己的仓库。",
|
|
825
|
+
"settings.enabled": "启用社区插件索引",
|
|
826
|
+
"settings.enabledHint": "此开关只控制索引列表的显示与否,关闭后在这里重新打开。",
|
|
827
|
+
"settings.inherit": "继承",
|
|
828
|
+
"settings.on": "开",
|
|
829
|
+
"settings.off": "关",
|
|
830
|
+
"settings.overridden": "已覆盖",
|
|
831
|
+
"settings.reset": "恢复默认",
|
|
832
|
+
"settings.notExposed": "当前 DSH 版本未向设置页暴露本插件的配置命名空间,表单不可用。可编辑 ~/.dsh/settings.yaml 直接配置,或为 dsh-host-apiproxy 的 WEB_SETTINGS_NAMESPACES 白名单补充本命名空间后重启。",
|
|
833
|
+
"settings.readOnly": "当前部署的设置只读。",
|
|
834
|
+
"settings.expand": "展开设置",
|
|
835
|
+
"settings.collapse": "收起设置",
|
|
836
|
+
"settings.save": "保存",
|
|
837
|
+
"settings.saving": "保存中…",
|
|
838
|
+
"settings.discard": "放弃",
|
|
839
|
+
"settings.unsaved": "未保存",
|
|
840
|
+
"settings.saveFailed": "部署未接受这些值,已保留供你修改。",
|
|
841
|
+
"settings.invalidNumber": "请输入数字,留空则使用默认值。",
|
|
842
|
+
"author": "作者",
|
|
843
|
+
"repository": "仓库",
|
|
844
|
+
"copy": "复制",
|
|
845
|
+
"copied": "已复制",
|
|
846
|
+
"installHint": "索引只登记、不安装代码。要运行某个插件,复制它的安装命令到终端执行;安装后,插件自带的开关与配置(若有)会出现在插件配置里。",
|
|
847
|
+
"empty": "暂无社区插件登记。",
|
|
848
|
+
"off": "社区插件索引已关闭。",
|
|
849
|
+
"notice": "条目由贡献者自行登记,与 dsh-web-ui 的发布内容无关;使用前请自行评估。"
|
|
850
|
+
};
|
|
851
|
+
/** English dictionary, checked complete against the zh key set. */
|
|
852
|
+
const en = {
|
|
853
|
+
"settings.title": "Community Plugins",
|
|
854
|
+
"settings.description": "Plugins developed and maintained by community contributors, linking to each author's own repository.",
|
|
855
|
+
"settings.enabled": "Enable the community plugin index",
|
|
856
|
+
"settings.enabledHint": "This switch only controls whether the index list is shown; turn it back on here.",
|
|
857
|
+
"settings.inherit": "Inherit",
|
|
858
|
+
"settings.on": "On",
|
|
859
|
+
"settings.off": "Off",
|
|
860
|
+
"settings.overridden": "Overridden",
|
|
861
|
+
"settings.reset": "Reset to default",
|
|
862
|
+
"settings.notExposed": "This DSH version does not expose this plugin's settings namespace to the configuration page, so the form is unavailable. Edit ~/.dsh/settings.yaml directly, or add the namespace to dsh-host-apiproxy's WEB_SETTINGS_NAMESPACES allowlist and restart.",
|
|
863
|
+
"settings.readOnly": "This deployment stores settings read-only.",
|
|
864
|
+
"settings.expand": "Show settings",
|
|
865
|
+
"settings.collapse": "Hide settings",
|
|
866
|
+
"settings.save": "Save",
|
|
867
|
+
"settings.saving": "Saving…",
|
|
868
|
+
"settings.discard": "Discard",
|
|
869
|
+
"settings.unsaved": "Unsaved",
|
|
870
|
+
"settings.saveFailed": "The deployment did not accept these values; they were left for you to correct.",
|
|
871
|
+
"settings.invalidNumber": "Enter a number, or leave blank to use the default.",
|
|
872
|
+
"author": "Author",
|
|
873
|
+
"repository": "Repository",
|
|
874
|
+
"copy": "Copy",
|
|
875
|
+
"copied": "Copied",
|
|
876
|
+
"installHint": "The index only registers entries, it never installs code. To run a plugin, copy its install command into a terminal; once installed, the plugin provides its own switch and config (if any) in the plugin configuration section.",
|
|
877
|
+
"empty": "No community plugins registered yet.",
|
|
878
|
+
"off": "The community plugin index is turned off.",
|
|
879
|
+
"notice": "Entries are contributed by their authors and are separate from dsh-web-ui releases; evaluate before use."
|
|
880
|
+
};
|
|
881
|
+
//#endregion
|
|
882
|
+
//#region src/client/index.ts
|
|
883
|
+
/** Settings namespace the card's enable switch edits (the Host plugin registers it). */
|
|
884
|
+
const COMMUNITY_PLUGINS_NS = "community-plugins";
|
|
885
|
+
/** Required services. */
|
|
886
|
+
const inject = [
|
|
887
|
+
"slots",
|
|
888
|
+
"locale",
|
|
889
|
+
"connection",
|
|
890
|
+
"settingsScope",
|
|
891
|
+
"remote"
|
|
892
|
+
];
|
|
893
|
+
/**
|
|
894
|
+
* Register the community plugin index as a first-level settings section, with
|
|
895
|
+
* its own enable switch over the community-plugins settings namespace.
|
|
896
|
+
* @param ctx - client root context.
|
|
897
|
+
*/
|
|
898
|
+
function apply(ctx) {
|
|
899
|
+
ctx.effect(() => ctx.locale.register("community-plugins", {
|
|
900
|
+
zh,
|
|
901
|
+
en
|
|
902
|
+
}), "community-plugins: dictionaries");
|
|
903
|
+
const controller = new CommunityPluginsCardController((ctx.get("webUiSettings") ?? ctx.settingsScope).bind({ namespace: COMMUNITY_PLUGINS_NS }));
|
|
904
|
+
ctx.slots.inject("settings.section", () => ctx.slots.register({
|
|
905
|
+
name: "settings.section",
|
|
906
|
+
id: "community-plugins",
|
|
907
|
+
order: 140,
|
|
908
|
+
label: () => ctx.locale.bind("community-plugins")("settings.title"),
|
|
909
|
+
locale: "community-plugins",
|
|
910
|
+
inject: () => controller.inject()
|
|
911
|
+
}, CommunityPluginsSection));
|
|
912
|
+
}
|
|
913
|
+
//#endregion
|
|
914
|
+
exports.apply = apply;
|
|
915
|
+
exports.inject = inject;
|
|
916
|
+
return module.exports;
|
|
917
|
+
}
|
|
918
|
+
});
|
|
919
|
+
|
|
920
|
+
//# sourceMappingURL=client.js.map
|