@dsh-plus/secret-env 0.1.25 → 0.1.27

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 CHANGED
@@ -4,8 +4,8 @@ window.__ModuleLoader__.load({
4
4
  var module = { exports: {} };
5
5
  var exports = module.exports;
6
6
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
- let react = require("react");
8
7
  let react_jsx_runtime = require("react/jsx-runtime");
8
+ let react = require("react");
9
9
  let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
10
10
  //#region src/client/panel-bus.ts
11
11
  const listeners = /* @__PURE__ */ new Set();
@@ -43,6 +43,109 @@ window.__ModuleLoader__.load({
43
43
  });
44
44
  }
45
45
  //#endregion
46
+ //#region ../shared/src/client/clipboard.ts
47
+ /**
48
+ * 剪贴板复制助手(浏览器半共享件):`navigator.clipboard` 优先,
49
+ * 非安全上下文(如非 localhost 直连的 HTTP 页面)无该 API 时退 `execCommand`。
50
+ * @module @dsh-plus/shared/client/clipboard
51
+ */
52
+ /** 复制文本到剪贴板;返回是否成功(失败静默,调用方自行决定是否提示)。 */
53
+ async function copyText(text) {
54
+ try {
55
+ await navigator.clipboard.writeText(text);
56
+ return true;
57
+ } catch {
58
+ const area = document.createElement("textarea");
59
+ area.value = text;
60
+ area.style.position = "fixed";
61
+ area.style.opacity = "0";
62
+ document.body.appendChild(area);
63
+ area.select();
64
+ const ok = document.execCommand("copy");
65
+ area.remove();
66
+ return ok;
67
+ }
68
+ }
69
+ //#endregion
70
+ //#region ../shared/src/client/draft.ts
71
+ /**
72
+ * 单命名空间 staged 草稿(简单配置卡片共用):读 scope 快照、播种草稿、
73
+ * 脏判定、保存(revision fencing + 重新装载)与状态行。
74
+ *
75
+ * 语义与官方 staged 表单一致:Host 更新不覆盖在途编辑,只有保存才写回;
76
+ * 保存成功后按 Host 解析值重置草稿。`from`/`patch` 参与 effect 与 useMemo
77
+ * 依赖,必须是模块级稳定函数(不要内联箭头函数)。
78
+ * @module @dsh-plus/shared/client/draft
79
+ */
80
+ //#endregion
81
+ //#region ../shared/src/client/fetch.ts
82
+ async function parse(res, guard) {
83
+ const body = await res.json();
84
+ if (!res.ok) throw new Error(body.error ?? `HTTP ${res.status}`);
85
+ if (guard !== void 0 && !guard(body)) throw new Error(`响应结构不符合预期(${res.url})`);
86
+ return body;
87
+ }
88
+ /** GET 同源 JSON 端点。 */
89
+ async function getJson(route, guard) {
90
+ return parse(await fetch(route, { credentials: "same-origin" }), guard);
91
+ }
92
+ /** POST 同源 JSON 端点(body 可省略)。 */
93
+ async function postJson(route, body, guard) {
94
+ return parse(await fetch(route, {
95
+ method: "POST",
96
+ credentials: "same-origin",
97
+ headers: { "content-type": "application/json" },
98
+ ...body === void 0 ? {} : { body: JSON.stringify(body) }
99
+ }), guard);
100
+ }
101
+ //#endregion
102
+ //#region ../shared/src/client/i18n.ts
103
+ /** 卡片通用文案(save/discard/unsaved/loading 等四插件共有键)。 */
104
+ const commonZh = {
105
+ save: "保存",
106
+ saving: "保存中…",
107
+ discard: "放弃",
108
+ unsaved: "未保存",
109
+ loading: "加载中…",
110
+ unavailable: "配置服务不可用。",
111
+ readOnly: "当前部署无 settings provider,配置为只读;请编辑 settings.yaml。",
112
+ invalidNumber: "请输入正整数。",
113
+ collapse: "收起",
114
+ expand: "展开",
115
+ enabledOn: "已启用",
116
+ enabledOff: "未启用",
117
+ saveFailed: "保存失败,请检查填写内容。",
118
+ confirm: "确认",
119
+ cancel: "取消"
120
+ };
121
+ const commonEn = {
122
+ save: "Save",
123
+ saving: "Saving…",
124
+ discard: "Discard",
125
+ unsaved: "Unsaved",
126
+ loading: "Loading…",
127
+ unavailable: "Settings service unavailable.",
128
+ readOnly: "No settings provider in this deployment; config is read-only. Edit settings.yaml.",
129
+ invalidNumber: "Enter a positive integer.",
130
+ collapse: "Collapse",
131
+ expand: "Expand",
132
+ enabledOn: "On",
133
+ enabledOff: "Off",
134
+ saveFailed: "Save failed; check the fields.",
135
+ confirm: "Confirm",
136
+ cancel: "Cancel"
137
+ };
138
+ /**
139
+ * own 覆盖同键合并(返回新对象,入参不可变)。
140
+ * 返回类型保留两侧键集合,调用方据此派生 DictKey。
141
+ */
142
+ function mergeDict(base, own) {
143
+ return {
144
+ ...base,
145
+ ...own
146
+ };
147
+ }
148
+ //#endregion
46
149
  //#region ../../node_modules/.pnpm/lucide-react@1.38.0_react@18.2.0/node_modules/lucide-react/dist/esm/shared/src/utils/mergeClasses.mjs
47
150
  /**
48
151
  * @license lucide-react v1.38.0 - ISC
@@ -222,117 +325,35 @@ window.__ModuleLoader__.load({
222
325
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EyeOff, { ...lucideProps(props) });
223
326
  }
224
327
  //#endregion
225
- //#region ../shared/src/client/draft.ts
226
- /**
227
- * 单命名空间 staged 草稿(简单配置卡片共用):读 scope 快照、播种草稿、
228
- * 脏判定、保存(revision fencing + 重新装载)与状态行。
229
- *
230
- * 语义与官方 staged 表单一致:Host 更新不覆盖在途编辑,只有保存才写回;
231
- * 保存成功后按 Host 解析值重置草稿。`from`/`patch` 参与 effect 与 useMemo
232
- * 依赖,必须是模块级稳定函数(不要内联箭头函数)。
233
- * @module @dsh-plus/shared/client/draft
234
- */
235
- //#endregion
236
- //#region ../shared/src/client/fetch.ts
237
- async function parse(res, guard) {
238
- const body = await res.json();
239
- if (!res.ok) throw new Error(body.error ?? `HTTP ${res.status}`);
240
- if (guard !== void 0 && !guard(body)) throw new Error(`响应结构不符合预期(${res.url})`);
241
- return body;
242
- }
243
- /** GET 同源 JSON 端点。 */
244
- async function getJson(route, guard) {
245
- return parse(await fetch(route, { credentials: "same-origin" }), guard);
246
- }
247
- /** POST 同源 JSON 端点(body 可省略)。 */
248
- async function postJson(route, body, guard) {
249
- return parse(await fetch(route, {
250
- method: "POST",
251
- credentials: "same-origin",
252
- headers: { "content-type": "application/json" },
253
- ...body === void 0 ? {} : { body: JSON.stringify(body) }
254
- }), guard);
255
- }
256
- //#endregion
257
- //#region ../shared/src/client/i18n.ts
258
- /** 卡片通用文案(save/discard/unsaved/loading 等四插件共有键)。 */
259
- const commonZh = {
260
- save: "保存",
261
- saving: "保存中…",
262
- discard: "放弃",
263
- unsaved: "未保存",
264
- loading: "加载中…",
265
- unavailable: "配置服务不可用。",
266
- readOnly: "当前部署无 settings provider,配置为只读;请编辑 settings.yaml。",
267
- invalidNumber: "请输入正整数。",
268
- collapse: "收起",
269
- expand: "展开",
270
- enabledOn: "已启用",
271
- enabledOff: "未启用",
272
- saveFailed: "保存失败,请检查填写内容。",
273
- confirm: "确认",
274
- cancel: "取消"
275
- };
276
- const commonEn = {
277
- save: "Save",
278
- saving: "Saving…",
279
- discard: "Discard",
280
- unsaved: "Unsaved",
281
- loading: "Loading…",
282
- unavailable: "Settings service unavailable.",
283
- readOnly: "No settings provider in this deployment; config is read-only. Edit settings.yaml.",
284
- invalidNumber: "Enter a positive integer.",
285
- collapse: "Collapse",
286
- expand: "Expand",
287
- enabledOn: "On",
288
- enabledOff: "Off",
289
- saveFailed: "Save failed; check the fields.",
290
- confirm: "Confirm",
291
- cancel: "Cancel"
292
- };
293
- /**
294
- * own 覆盖同键合并(返回新对象,入参不可变)。
295
- * 返回类型保留两侧键集合,调用方据此派生 DictKey。
296
- */
297
- function mergeDict(base, own) {
298
- return {
299
- ...base,
300
- ...own
301
- };
302
- }
303
- //#endregion
304
328
  //#region ../shared/src/client/styles.ts
305
329
  /**
306
- * 配置卡片样式工厂(各插件 client 的公共收编版)。
307
- * 视觉规则与迁移前逐字一致(类名前缀换为各插件自身,DOM 类名不变),
308
- * 另补移动端与可达性增强(767px 断点 + pointer:coarse,对齐 ui-mobile-fit 约定):
330
+ * 配置分节样式工厂(各插件 client 的公共收编版)。
331
+ *
332
+ * 目标宿主 dsh 0.2.1-alpha.1:插件页把配置视图放进已有页面级内边距的容器里
333
+ * (`div.detailSections[data-plugin-config]` 或 `section.detailSection[data-plugin-config]`),
334
+ * 外层无边框——故这里输出的是**无边框分节**(小节标题 13px/600、字段 12px 0、
335
+ * 横向零内边距、页脚非 sticky),度量对齐官方设置页
336
+ * (`@deepseek-ai/dsh-client-ui-primitives` 的 settings-form)。
337
+ *
338
+ * 移动端与可达性(767px 断点 + pointer:coarse,对齐 ui-mobile-fit 约定):
309
339
  * - 窄屏 input/textarea 16px(防 iOS 聚焦缩放)、可点目标 ≥44px;
310
- * - footer 按钮等宽换行、状态文字独占一行;
311
- * - 展开态 body 底部 sticky 保存栏(长表单滚动中始终可达)。
312
- * - 分节形态(-section,插件页 page 视图内嵌宿主页面):无边框/背景、横向零
313
- * 内边距,页脚不 sticky——官方插件页已提供页面级内边距,再叠一层卡片会
314
- * 缩窄移动端可用宽度(卡片套卡片)。
340
+ * - 页脚按钮等宽换行、状态文字独占一行。
315
341
  * 沿用官方 data-plugin / data-plugin-css 约定(HMR 据此卸载),不覆盖上游选择器。
316
342
  * @module @dsh-plus/shared/client/styles
317
343
  */
318
- /** 生成全套卡片 CSS;prefix 为插件类名前缀(如 'dne'),extra 为插件特有规则。 */
344
+ /** 生成全套分节 CSS;prefix 为插件类名前缀(如 'dne'),extra 为插件特有规则。 */
319
345
  function cardCss(prefix, extra = "") {
320
346
  return `
321
- .${prefix}-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}
322
- .${prefix}-card:hover{border-color:var(--dsw-alias-label-dimmed)}
323
- .${prefix}-cardOpen{background:var(--dsw-alias-bg-layer-2);border-color:var(--dsw-alias-label-dimmed)}
324
- .${prefix}-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}
325
- .${prefix}-header:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:-2px}
326
- .${prefix}-headText{flex-direction:column;flex:1;gap:4px;min-width:0;display:flex}
327
- .${prefix}-name{color:var(--dsw-alias-label-primary);font-size:15px;font-weight:600;line-height:1.4}
328
- .${prefix}-description{color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:1.5}
329
- .${prefix}-chevron{color:var(--dsw-alias-label-tertiary);flex:none;transition:transform .16s;display:inline-flex}
330
- .${prefix}-chevronOpen{transform:rotate(180deg)}
347
+ .${prefix}-section{box-sizing:border-box;flex-direction:column;width:100%;min-width:0;padding:16px 0;display:flex}
348
+ .${prefix}-sectionHead{align-items:center;gap:8px;min-width:0;display:flex}
349
+ .${prefix}-sectionTitle{min-width:0;color:var(--dsw-alias-label-primary);flex:1;margin:0;font-size:13px;font-weight:600;line-height:1.5}
350
+ .${prefix}-sectionBadge{white-space:nowrap;border-radius:999px;flex:none;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}
351
+ .${prefix}-sectionDesc{color:var(--dsw-alias-label-tertiary);margin:4px 0 0;font-size:12px;line-height:1.5}
352
+ .${prefix}-sectionBody{flex-direction:column;min-width:0;display:flex}
353
+ .${prefix}-sectionFooter{align-items:center;gap:8px;padding-top:16px;display:flex;flex-wrap:wrap}
331
354
  .${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}
332
- .${prefix}-statusBadge{white-space:nowrap;border-radius:999px;flex:none;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}
333
355
  .${prefix}-statusOn{background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary)}
334
356
  .${prefix}-statusOff{color:var(--dsw-alias-label-tertiary)}
335
- .${prefix}-body{border-top:1px solid var(--dsw-alias-border-l2);margin:0 16px;padding-bottom:8px}
336
357
  .${prefix}-field{flex-direction:column;gap:6px;padding:12px 0;display:flex}
337
358
  .${prefix}-field+.${prefix}-field{border-top:1px solid var(--dsw-alias-border-l2)}
338
359
  .${prefix}-head{align-items:center;gap:8px;display:flex}
@@ -358,19 +379,9 @@ window.__ModuleLoader__.load({
358
379
  .${prefix}-checkRow input:checked::after{left:16px;background:var(--dsw-alias-bg-base)}
359
380
  .${prefix}-checkRow input:disabled{opacity:.4;cursor:default}
360
381
  .${prefix}-checkRow input:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px}
361
- /* 宿主内嵌分节形态(插件页 page 视图):官方设置页同构——无边框/背景,
362
- 横向零内边距(宽度交给宿主页面容器),小节标题 13px/600,页脚非 sticky。 */
363
- .${prefix}-section{box-sizing:border-box;flex-direction:column;width:100%;min-width:0;padding:16px 0;display:flex}
364
- .${prefix}-sectionHead{align-items:center;gap:8px;min-width:0;display:flex}
365
- .${prefix}-sectionTitle{min-width:0;color:var(--dsw-alias-label-primary);flex:1;margin:0;font-size:13px;font-weight:600;line-height:1.5}
366
- .${prefix}-sectionBadge{white-space:nowrap;border-radius:999px;flex:none;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}
367
- .${prefix}-sectionDesc{color:var(--dsw-alias-label-tertiary);margin:4px 0 0;font-size:12px;line-height:1.5}
368
- .${prefix}-sectionBody{flex-direction:column;min-width:0;display:flex}
369
- .${prefix}-sectionFooter{align-items:center;gap:8px;padding-top:16px;display:flex;flex-wrap:wrap}
370
382
  .${prefix}-groupLabel{color:var(--dsw-alias-label-secondary);font-size:12px;font-weight:600;padding:12px 0 2px;margin:0}
371
383
  .${prefix}-readOnly{color:var(--dsw-alias-label-tertiary);margin:12px 0 0;font-size:12px;line-height:1.5}
372
384
  .${prefix}-warn{color:var(--dsw-alias-label-error);margin:12px 0 0;font-size:12px;line-height:1.5}
373
- .${prefix}-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;flex-wrap:wrap}
374
385
  .${prefix}-status{min-width:0;color:var(--dsw-alias-label-secondary);flex:1;margin:0;font-size:12px;line-height:1.5}
375
386
  .${prefix}-statusError{color:var(--dsw-alias-label-error)}
376
387
  .${prefix}-btn{appearance:none;font:inherit;cursor:pointer;border:1px solid #0000;border-radius:8px;padding:5px 14px;font-size:13px;line-height:1.5}
@@ -380,16 +391,10 @@ window.__ModuleLoader__.load({
380
391
  .${prefix}-btn:disabled{opacity:.4;cursor:default}
381
392
  .${prefix}-btn:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}
382
393
  @media (max-width:767px){
383
- .${prefix}-body{margin:0 10px}
384
- .${prefix}-header{padding:12px 12px}
385
394
  .${prefix}-input,.${prefix}-select{font-size:16px;height:40px}
386
395
  .${prefix}-checkRow{min-height:44px;padding:8px 0}
387
- .${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}
388
396
  .${prefix}-status{flex-basis:100%;flex:none}
389
397
  .${prefix}-btn{min-height:44px;flex:1;min-width:96px}
390
- .${prefix}-statusBadge{display:none}
391
- .${prefix}-sectionBadge{display:inline-block}
392
- .${prefix}-sectionFooter{position:static}
393
398
  }
394
399
  @media (pointer:coarse){
395
400
  .${prefix}-btn{min-height:44px}
@@ -937,8 +942,9 @@ ${extra}
937
942
  //#endregion
938
943
  //#region src/client/common.ts
939
944
  /**
940
- * 浏览器半共享小件:错误码文案映射、复制助手与变量名输入规则
945
+ * 浏览器半共享小件:错误码文案映射、变量名输入规则
941
946
  * (设置页与会话控件共用;名称规则与 node 半 names.ts 同源)。
947
+ * 复制助手已收编到 `@dsh-plus/shared/client`(llm-pi 目录浏览器是第二个使用方)。
942
948
  * @module secret-env/client/common
943
949
  */
944
950
  /** 端点错误 → 本地化文案(未知码回落 internal)。 */
@@ -955,23 +961,6 @@ ${extra}
955
961
  if (raw.trim() === "") return null;
956
962
  return validateSuffix(normalizeSuffix(raw));
957
963
  }
958
- /** 复制文本到剪贴板(clipboard API 缺席时退 execCommand)。 */
959
- async function copyText(text) {
960
- try {
961
- await navigator.clipboard.writeText(text);
962
- return true;
963
- } catch {
964
- const area = document.createElement("textarea");
965
- area.value = text;
966
- area.style.position = "fixed";
967
- area.style.opacity = "0";
968
- document.body.appendChild(area);
969
- area.select();
970
- const ok = document.execCommand("copy");
971
- area.remove();
972
- return ok;
973
- }
974
- }
975
964
  //#endregion
976
965
  //#region src/client/panel.tsx
977
966
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dsh-plus/secret-env",
3
- "version": "0.1.25",
3
+ "version": "0.1.27",
4
4
  "description": "dsh-plus service+ui plugin: 密钥环境变量——以 $DSH_VAR_* 变量名向 agent 暴露密钥(全局持久/会话级/一次性),执行期经 dsh-shell-env 原生注入,值不进消息流,不影响缓存率",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
@@ -19,27 +19,27 @@
19
19
  "src"
20
20
  ],
21
21
  "dependencies": {
22
- "@dsh-plus/shared": "0.1.31"
22
+ "@dsh-plus/shared": "0.1.33"
23
23
  },
24
24
  "peerDependencies": {
25
- "@deepseek-ai/cordis": "^4.0.4",
26
- "@deepseek-ai/dsh-credentials": "^0.2.0-rc.1",
27
- "@deepseek-ai/dsh-host-webserver": "^0.2.0-rc.1",
28
- "@deepseek-ai/dsh-settings": "^0.2.0-rc.1",
29
- "@deepseek-ai/dsh-shell-env": "^0.2.0-rc.1",
30
- "@deepseek-ai/schemastery": "^3.18.4"
25
+ "@deepseek-ai/cordis": "^4.0.5-alpha.1",
26
+ "@deepseek-ai/dsh-credentials": "^0.2.1-alpha.1",
27
+ "@deepseek-ai/dsh-host-webserver": "^0.2.1-alpha.1",
28
+ "@deepseek-ai/dsh-settings": "^0.2.1-alpha.1",
29
+ "@deepseek-ai/dsh-shell-env": "^0.2.1-alpha.1",
30
+ "@deepseek-ai/schemastery": "^3.18.5-alpha.1"
31
31
  },
32
32
  "devDependencies": {
33
- "@deepseek-ai/cordis": "4.0.4",
34
- "@deepseek-ai/dsh-client-ui-commands": "0.2.0-rc.1",
35
- "@deepseek-ai/dsh-client-ui-primitives": "0.2.0-rc.1",
36
- "@deepseek-ai/dsh-credentials": "0.2.0-rc.1",
37
- "@deepseek-ai/dsh-host-webserver": "0.2.0-rc.1",
38
- "@deepseek-ai/dsh-session": "0.2.0-rc.1",
39
- "@deepseek-ai/dsh-settings": "0.2.0-rc.1",
40
- "@deepseek-ai/dsh-shell-env": "0.2.0-rc.1",
41
- "@deepseek-ai/dsh-tools": "0.2.0-rc.1",
42
- "@deepseek-ai/schemastery": "3.18.4",
33
+ "@deepseek-ai/cordis": "4.0.5-alpha.1",
34
+ "@deepseek-ai/dsh-client-ui-commands": "0.2.1-alpha.1",
35
+ "@deepseek-ai/dsh-client-ui-primitives": "0.2.1-alpha.1",
36
+ "@deepseek-ai/dsh-credentials": "0.2.1-alpha.1",
37
+ "@deepseek-ai/dsh-host-webserver": "0.2.1-alpha.1",
38
+ "@deepseek-ai/dsh-session": "0.2.1-alpha.1",
39
+ "@deepseek-ai/dsh-settings": "0.2.1-alpha.1",
40
+ "@deepseek-ai/dsh-shell-env": "0.2.1-alpha.1",
41
+ "@deepseek-ai/dsh-tools": "0.2.1-alpha.1",
42
+ "@deepseek-ai/schemastery": "3.18.5-alpha.1",
43
43
  "@types/node": "26.4.0",
44
44
  "@types/react": "~18.3.31",
45
45
  "react": "^18.2.0"
@@ -1,12 +1,17 @@
1
1
  /**
2
- * 浏览器半共享小件:错误码文案映射、复制助手与变量名输入规则
2
+ * 浏览器半共享小件:错误码文案映射、变量名输入规则
3
3
  * (设置页与会话控件共用;名称规则与 node 半 names.ts 同源)。
4
+ * 复制助手已收编到 `@dsh-plus/shared/client`(llm-pi 目录浏览器是第二个使用方)。
4
5
  * @module secret-env/client/common
5
6
  */
7
+ import { copyText } from '@dsh-plus/shared/client'
8
+
6
9
  import { type NameError, normalizeSuffix, validateSuffix } from '../names.ts'
7
10
  import { ApiError } from './api.ts'
8
11
  import { errorKeyOf, type Translate } from './i18n.ts'
9
12
 
13
+ export { copyText }
14
+
10
15
  /** 端点错误 → 本地化文案(未知码回落 internal)。 */
11
16
  export function errorText(t: Translate, error: unknown): string {
12
17
  const code = error instanceof ApiError ? error.code : 'internal'
@@ -25,22 +30,3 @@ export function nameErrorOf(raw: string): NameError | null {
25
30
  if (raw.trim() === '') return null
26
31
  return validateSuffix(normalizeSuffix(raw))
27
32
  }
28
-
29
- /** 复制文本到剪贴板(clipboard API 缺席时退 execCommand)。 */
30
- export async function copyText(text: string): Promise<boolean> {
31
- try {
32
- await navigator.clipboard.writeText(text)
33
- return true
34
- } catch {
35
- // 回落:非安全上下文(如非 localhost 直连)无 clipboard API。
36
- const area = document.createElement('textarea')
37
- area.value = text
38
- area.style.position = 'fixed'
39
- area.style.opacity = '0'
40
- document.body.appendChild(area)
41
- area.select()
42
- const ok = document.execCommand('copy')
43
- area.remove()
44
- return ok
45
- }
46
- }