@dsh-plus/notify-email 0.1.36 → 0.1.38
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 +349 -282
- package/package.json +2 -2
- package/src/client/card.tsx +124 -264
- package/src/client/client.ts +2 -2
- package/src/client/draft.ts +99 -0
- package/src/client/fields.tsx +153 -0
- package/src/ns.ts +2 -2
package/lib/client.js
CHANGED
|
@@ -4,9 +4,9 @@ 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");
|
|
9
|
-
let
|
|
8
|
+
let react = require("react");
|
|
9
|
+
require("@deepseek-ai/dsh-client-ui-primitives");
|
|
10
10
|
//#region ../shared/src/client/card.tsx
|
|
11
11
|
const IDLE_STATUS = {
|
|
12
12
|
kind: "idle",
|
|
@@ -15,60 +15,68 @@ window.__ModuleLoader__.load({
|
|
|
15
15
|
function CardChrome(props) {
|
|
16
16
|
const p = props.prefix;
|
|
17
17
|
const badge = props.statusBadge;
|
|
18
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("
|
|
19
|
-
className: `${p}-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
28
|
-
className: `${p}-headText`,
|
|
29
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
30
|
-
className: `${p}-name`,
|
|
18
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
19
|
+
className: `${p}-section`,
|
|
20
|
+
"aria-label": props.title,
|
|
21
|
+
children: [
|
|
22
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
23
|
+
className: `${p}-sectionHead`,
|
|
24
|
+
children: [
|
|
25
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
|
|
26
|
+
className: `${p}-sectionTitle`,
|
|
31
27
|
children: props.title
|
|
32
|
-
}),
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
role: "status",
|
|
53
|
-
children: props.readOnlyNotice
|
|
54
|
-
}) : null,
|
|
55
|
-
props.children,
|
|
56
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
57
|
-
className: `${p}-footer`,
|
|
58
|
-
children: [props.status !== void 0 && props.status.kind !== "idle" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
59
|
-
className: `${p}-status${props.status.kind === "error" ? ` ${p}-statusError` : ""}`,
|
|
28
|
+
}),
|
|
29
|
+
badge !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
30
|
+
className: `${p}-sectionBadge ${badge.on ? `${p}-statusOn` : `${p}-statusOff`}`,
|
|
31
|
+
children: badge.text
|
|
32
|
+
}) : null,
|
|
33
|
+
props.dirty ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
34
|
+
className: `${p}-pending`,
|
|
35
|
+
children: props.dirtyLabel
|
|
36
|
+
}) : null
|
|
37
|
+
]
|
|
38
|
+
}),
|
|
39
|
+
props.description === "" ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
40
|
+
className: `${p}-sectionDesc`,
|
|
41
|
+
children: props.description
|
|
42
|
+
}),
|
|
43
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
44
|
+
className: `${p}-sectionBody`,
|
|
45
|
+
children: [
|
|
46
|
+
props.readOnlyNotice !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
47
|
+
className: `${p}-readOnly`,
|
|
60
48
|
role: "status",
|
|
61
|
-
children: props.
|
|
62
|
-
}) : null,
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
49
|
+
children: props.readOnlyNotice
|
|
50
|
+
}) : null,
|
|
51
|
+
props.children,
|
|
52
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
53
|
+
className: `${p}-sectionFooter`,
|
|
54
|
+
children: [props.status !== void 0 && props.status.kind !== "idle" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
55
|
+
className: `${p}-status${props.status.kind === "error" ? ` ${p}-statusError` : ""}`,
|
|
56
|
+
role: "status",
|
|
57
|
+
children: props.status.text
|
|
58
|
+
}) : null, props.actions.map((action) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
59
|
+
type: "button",
|
|
60
|
+
className: `${p}-btn ${action.variant === "primary" ? `${p}-btnPrimary` : `${p}-btnGhost`}`,
|
|
61
|
+
disabled: action.disabled === true,
|
|
62
|
+
onClick: action.onClick,
|
|
63
|
+
children: action.label
|
|
64
|
+
}, action.key))]
|
|
65
|
+
})
|
|
66
|
+
]
|
|
67
|
+
})
|
|
68
|
+
]
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
/** 取值未到达时的占位:与 CardChrome 同形态,避免宿主页里闪一次卡片外壳。 */
|
|
72
|
+
function CardLoading(props) {
|
|
73
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("section", {
|
|
74
|
+
className: `${props.prefix}-section`,
|
|
75
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
76
|
+
className: `${props.prefix}-readOnly`,
|
|
77
|
+
role: "status",
|
|
78
|
+
children: props.text
|
|
79
|
+
})
|
|
72
80
|
});
|
|
73
81
|
}
|
|
74
82
|
//#endregion
|
|
@@ -76,19 +84,14 @@ window.__ModuleLoader__.load({
|
|
|
76
84
|
/** dsh-plus 聚合 bundle 的包名(各插件 row 均经其补丁层插入 profile)。 */
|
|
77
85
|
const DSH_PLUS_BUNDLE = "@dsh-plus/bundle-main";
|
|
78
86
|
/**
|
|
79
|
-
*
|
|
80
|
-
*
|
|
87
|
+
* 把一张配置卡片注册进插件页的两个配置槽位。
|
|
88
|
+
* 两个槽位都由 0.2.1-alpha.1 的插件页声明,任一缺席即报错——不做版本探测。
|
|
81
89
|
*/
|
|
82
90
|
function injectPluginConfigCard(slots, reg) {
|
|
83
91
|
const base = {
|
|
84
92
|
locale: reg.ns,
|
|
85
93
|
inject: reg.inject
|
|
86
94
|
};
|
|
87
|
-
slots.inject("settings.plugin.item", () => slots.register({
|
|
88
|
-
name: "settings.plugin.item",
|
|
89
|
-
key: reg.ns,
|
|
90
|
-
...base
|
|
91
|
-
}, reg.component));
|
|
92
95
|
slots.inject("plugins.row.config", () => slots.register({
|
|
93
96
|
name: "plugins.row.config",
|
|
94
97
|
key: `${DSH_PLUS_BUNDLE}#${reg.rowId}`,
|
|
@@ -101,6 +104,17 @@ window.__ModuleLoader__.load({
|
|
|
101
104
|
}, reg.component));
|
|
102
105
|
}
|
|
103
106
|
//#endregion
|
|
107
|
+
//#region ../shared/src/client/draft.ts
|
|
108
|
+
/**
|
|
109
|
+
* 单命名空间 staged 草稿(简单配置卡片共用):读 scope 快照、播种草稿、
|
|
110
|
+
* 脏判定、保存(revision fencing + 重新装载)与状态行。
|
|
111
|
+
*
|
|
112
|
+
* 语义与官方 staged 表单一致:Host 更新不覆盖在途编辑,只有保存才写回;
|
|
113
|
+
* 保存成功后按 Host 解析值重置草稿。`from`/`patch` 参与 effect 与 useMemo
|
|
114
|
+
* 依赖,必须是模块级稳定函数(不要内联箭头函数)。
|
|
115
|
+
* @module @dsh-plus/shared/client/draft
|
|
116
|
+
*/
|
|
117
|
+
//#endregion
|
|
104
118
|
//#region ../shared/src/client/fields.tsx
|
|
105
119
|
function TextField(props) {
|
|
106
120
|
const p = props.prefix ?? "dshp";
|
|
@@ -298,33 +312,33 @@ window.__ModuleLoader__.load({
|
|
|
298
312
|
//#endregion
|
|
299
313
|
//#region ../shared/src/client/styles.ts
|
|
300
314
|
/**
|
|
301
|
-
*
|
|
302
|
-
*
|
|
303
|
-
*
|
|
315
|
+
* 配置分节样式工厂(各插件 client 的公共收编版)。
|
|
316
|
+
*
|
|
317
|
+
* 目标宿主 dsh 0.2.1-alpha.1:插件页把配置视图放进已有页面级内边距的容器里
|
|
318
|
+
* (`div.detailSections[data-plugin-config]` 或 `section.detailSection[data-plugin-config]`),
|
|
319
|
+
* 外层无边框——故这里输出的是**无边框分节**(小节标题 13px/600、字段 12px 0、
|
|
320
|
+
* 横向零内边距、页脚非 sticky),度量对齐官方设置页
|
|
321
|
+
* (`@deepseek-ai/dsh-client-ui-primitives` 的 settings-form)。
|
|
322
|
+
*
|
|
323
|
+
* 移动端与可达性(767px 断点 + pointer:coarse,对齐 ui-mobile-fit 约定):
|
|
304
324
|
* - 窄屏 input/textarea 16px(防 iOS 聚焦缩放)、可点目标 ≥44px;
|
|
305
|
-
* -
|
|
306
|
-
* - 展开态 body 底部 sticky 保存栏(长表单滚动中始终可达)。
|
|
325
|
+
* - 页脚按钮等宽换行、状态文字独占一行。
|
|
307
326
|
* 沿用官方 data-plugin / data-plugin-css 约定(HMR 据此卸载),不覆盖上游选择器。
|
|
308
327
|
* @module @dsh-plus/shared/client/styles
|
|
309
328
|
*/
|
|
310
|
-
/**
|
|
329
|
+
/** 生成全套分节 CSS;prefix 为插件类名前缀(如 'dne'),extra 为插件特有规则。 */
|
|
311
330
|
function cardCss(prefix, extra = "") {
|
|
312
331
|
return `
|
|
313
|
-
.${prefix}-
|
|
314
|
-
.${prefix}-
|
|
315
|
-
.${prefix}-
|
|
316
|
-
.${prefix}-
|
|
317
|
-
.${prefix}-
|
|
318
|
-
.${prefix}-
|
|
319
|
-
.${prefix}-
|
|
320
|
-
.${prefix}-description{color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:1.5}
|
|
321
|
-
.${prefix}-chevron{color:var(--dsw-alias-label-tertiary);flex:none;transition:transform .16s;display:inline-flex}
|
|
322
|
-
.${prefix}-chevronOpen{transform:rotate(180deg)}
|
|
332
|
+
.${prefix}-section{box-sizing:border-box;flex-direction:column;width:100%;min-width:0;padding:16px 0;display:flex}
|
|
333
|
+
.${prefix}-sectionHead{align-items:center;gap:8px;min-width:0;display:flex}
|
|
334
|
+
.${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}
|
|
335
|
+
.${prefix}-sectionBadge{white-space:nowrap;border-radius:999px;flex:none;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}
|
|
336
|
+
.${prefix}-sectionDesc{color:var(--dsw-alias-label-tertiary);margin:4px 0 0;font-size:12px;line-height:1.5}
|
|
337
|
+
.${prefix}-sectionBody{flex-direction:column;min-width:0;display:flex}
|
|
338
|
+
.${prefix}-sectionFooter{align-items:center;gap:8px;padding-top:16px;display:flex;flex-wrap:wrap}
|
|
323
339
|
.${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}
|
|
324
|
-
.${prefix}-statusBadge{white-space:nowrap;border-radius:999px;flex:none;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}
|
|
325
340
|
.${prefix}-statusOn{background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary)}
|
|
326
341
|
.${prefix}-statusOff{color:var(--dsw-alias-label-tertiary)}
|
|
327
|
-
.${prefix}-body{border-top:1px solid var(--dsw-alias-border-l2);margin:0 16px;padding-bottom:8px}
|
|
328
342
|
.${prefix}-field{flex-direction:column;gap:6px;padding:12px 0;display:flex}
|
|
329
343
|
.${prefix}-field+.${prefix}-field{border-top:1px solid var(--dsw-alias-border-l2)}
|
|
330
344
|
.${prefix}-head{align-items:center;gap:8px;display:flex}
|
|
@@ -353,7 +367,6 @@ window.__ModuleLoader__.load({
|
|
|
353
367
|
.${prefix}-groupLabel{color:var(--dsw-alias-label-secondary);font-size:12px;font-weight:600;padding:12px 0 2px;margin:0}
|
|
354
368
|
.${prefix}-readOnly{color:var(--dsw-alias-label-tertiary);margin:12px 0 0;font-size:12px;line-height:1.5}
|
|
355
369
|
.${prefix}-warn{color:var(--dsw-alias-label-error);margin:12px 0 0;font-size:12px;line-height:1.5}
|
|
356
|
-
.${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}
|
|
357
370
|
.${prefix}-status{min-width:0;color:var(--dsw-alias-label-secondary);flex:1;margin:0;font-size:12px;line-height:1.5}
|
|
358
371
|
.${prefix}-statusError{color:var(--dsw-alias-label-error)}
|
|
359
372
|
.${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}
|
|
@@ -363,14 +376,10 @@ window.__ModuleLoader__.load({
|
|
|
363
376
|
.${prefix}-btn:disabled{opacity:.4;cursor:default}
|
|
364
377
|
.${prefix}-btn:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}
|
|
365
378
|
@media (max-width:767px){
|
|
366
|
-
.${prefix}-body{margin:0 10px}
|
|
367
|
-
.${prefix}-header{padding:12px 12px}
|
|
368
379
|
.${prefix}-input,.${prefix}-select{font-size:16px;height:40px}
|
|
369
380
|
.${prefix}-checkRow{min-height:44px;padding:8px 0}
|
|
370
|
-
.${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}
|
|
371
381
|
.${prefix}-status{flex-basis:100%;flex:none}
|
|
372
382
|
.${prefix}-btn{min-height:44px;flex:1;min-width:96px}
|
|
373
|
-
.${prefix}-statusBadge{display:none}
|
|
374
383
|
}
|
|
375
384
|
@media (pointer:coarse){
|
|
376
385
|
.${prefix}-btn{min-height:44px}
|
|
@@ -401,8 +410,8 @@ ${extra}
|
|
|
401
410
|
* settings 命名空间字面量(纯常量,零依赖,浏览器半可安全引入)。
|
|
402
411
|
* 服务端直接以字面量注册(0.1.2-alpha.2 起 dsh-settings 对命名空间做编译期
|
|
403
412
|
* 语法校验,见 config.ts);浏览器半把它作为配置卡片的注册键
|
|
404
|
-
*
|
|
405
|
-
*
|
|
413
|
+
* (插件页配置卡片挂在 row/bundle config 槽位上,与命名空间解耦,
|
|
414
|
+
* 见 shared/config-slots),
|
|
406
415
|
* 同一字面量两处共用,防止漂移。
|
|
407
416
|
* @module notify-email/ns
|
|
408
417
|
*/
|
|
@@ -422,20 +431,18 @@ ${extra}
|
|
|
422
431
|
}));
|
|
423
432
|
}
|
|
424
433
|
//#endregion
|
|
425
|
-
//#region src/client/
|
|
426
|
-
/**
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
* @module notify-email/client/card
|
|
438
|
-
*/
|
|
434
|
+
//#region src/client/draft.ts
|
|
435
|
+
/** 通知触发点(key 同时是 i18n 文案键与 settings 字段名)。 */
|
|
436
|
+
const TRIGGER_KEYS = [
|
|
437
|
+
"onComplete",
|
|
438
|
+
"onError",
|
|
439
|
+
"onAborted",
|
|
440
|
+
"onQuestion",
|
|
441
|
+
"onPlanReview"
|
|
442
|
+
];
|
|
443
|
+
function isPositiveInt(text) {
|
|
444
|
+
return /^[0-9]+$/.test(text) && Number(text) > 0;
|
|
445
|
+
}
|
|
439
446
|
function draftFromValue(value) {
|
|
440
447
|
return {
|
|
441
448
|
enabled: value.enabled,
|
|
@@ -452,9 +459,7 @@ ${extra}
|
|
|
452
459
|
dryRun: value.dryRun
|
|
453
460
|
};
|
|
454
461
|
}
|
|
455
|
-
|
|
456
|
-
return /^[0-9]+$/.test(text) && Number(text) > 0;
|
|
457
|
-
}
|
|
462
|
+
/** 草稿 → 提交形状(pass 为空表示「保持已存密钥」,由调用方剔除该键)。 */
|
|
458
463
|
function toPatch(draft) {
|
|
459
464
|
return {
|
|
460
465
|
enabled: draft.enabled,
|
|
@@ -473,27 +478,159 @@ ${extra}
|
|
|
473
478
|
dryRun: draft.dryRun
|
|
474
479
|
};
|
|
475
480
|
}
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
const
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
481
|
+
//#endregion
|
|
482
|
+
//#region src/client/fields.tsx
|
|
483
|
+
/**
|
|
484
|
+
* 「邮件通知」卡片的字段组:SMTP 连接字段与触发/限流字段。
|
|
485
|
+
* 从 card.tsx 拆出,避免单组件过长;控件走 @dsh-plus/shared/client 套件。
|
|
486
|
+
* @module notify-email/client/fields
|
|
487
|
+
*/
|
|
488
|
+
/** SMTP 连接字段(密码为只写:留空保持已存密钥)。 */
|
|
489
|
+
function SmtpFields(props) {
|
|
490
|
+
const { t, draft, disabled, onEdit } = props;
|
|
491
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
492
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(TextField, {
|
|
493
|
+
prefix: "dne",
|
|
494
|
+
id: "dne-host",
|
|
495
|
+
label: t("host"),
|
|
496
|
+
hint: t("hostHint"),
|
|
497
|
+
value: draft.host,
|
|
498
|
+
disabled,
|
|
499
|
+
onEdit: (v) => onEdit("host", v)
|
|
500
|
+
}),
|
|
501
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(TextField, {
|
|
502
|
+
prefix: "dne",
|
|
503
|
+
id: "dne-port",
|
|
504
|
+
label: t("port"),
|
|
505
|
+
hint: t("portHint"),
|
|
506
|
+
value: draft.port,
|
|
507
|
+
numeric: true,
|
|
508
|
+
disabled,
|
|
509
|
+
invalid: !isPositiveInt(draft.port),
|
|
510
|
+
invalidLabel: t("invalidNumber"),
|
|
511
|
+
onEdit: (v) => onEdit("port", v)
|
|
512
|
+
}),
|
|
513
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(CheckRow, {
|
|
514
|
+
prefix: "dne",
|
|
515
|
+
id: "dne-secure",
|
|
516
|
+
label: t("secure"),
|
|
517
|
+
checked: draft.secure,
|
|
518
|
+
disabled,
|
|
519
|
+
onEdit: (v) => onEdit("secure", v)
|
|
520
|
+
}),
|
|
521
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(TextField, {
|
|
522
|
+
prefix: "dne",
|
|
523
|
+
id: "dne-user",
|
|
524
|
+
label: t("user"),
|
|
525
|
+
hint: t("userHint"),
|
|
526
|
+
value: draft.user,
|
|
527
|
+
disabled,
|
|
528
|
+
onEdit: (v) => onEdit("user", v)
|
|
529
|
+
}),
|
|
530
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(TextField, {
|
|
531
|
+
prefix: "dne",
|
|
532
|
+
id: "dne-pass",
|
|
533
|
+
label: t("pass"),
|
|
534
|
+
hint: t("passHint"),
|
|
535
|
+
value: draft.pass,
|
|
536
|
+
password: true,
|
|
537
|
+
disabled,
|
|
538
|
+
badge: {
|
|
539
|
+
text: props.passConfigured ? t("passSet") : t("passUnset"),
|
|
540
|
+
set: props.passConfigured
|
|
541
|
+
},
|
|
542
|
+
onEdit: (v) => onEdit("pass", v)
|
|
543
|
+
}),
|
|
544
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(TextField, {
|
|
545
|
+
prefix: "dne",
|
|
546
|
+
id: "dne-from",
|
|
547
|
+
label: t("from"),
|
|
548
|
+
hint: t("fromHint"),
|
|
549
|
+
value: draft.from,
|
|
550
|
+
disabled,
|
|
551
|
+
onEdit: (v) => onEdit("from", v)
|
|
552
|
+
})
|
|
553
|
+
] });
|
|
554
|
+
}
|
|
555
|
+
/** 收件人、触发点开关与限流字段。 */
|
|
556
|
+
function TriggerFields(props) {
|
|
557
|
+
const { t, draft, disabled, onEdit } = props;
|
|
558
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
559
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(TextField, {
|
|
560
|
+
prefix: "dne",
|
|
561
|
+
id: "dne-to",
|
|
562
|
+
label: t("to"),
|
|
563
|
+
hint: t("toHint"),
|
|
564
|
+
value: draft.toText,
|
|
565
|
+
disabled,
|
|
566
|
+
onEdit: (v) => onEdit("toText", v)
|
|
567
|
+
}),
|
|
568
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
569
|
+
className: "dne-groupLabel",
|
|
570
|
+
children: t("triggerGroup")
|
|
571
|
+
}),
|
|
572
|
+
TRIGGER_KEYS.map((key) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CheckRow, {
|
|
573
|
+
prefix: "dne",
|
|
574
|
+
id: `dne-${key}`,
|
|
575
|
+
label: t(key),
|
|
576
|
+
checked: draft.triggers[key],
|
|
577
|
+
disabled,
|
|
578
|
+
onEdit: (v) => props.onTrigger(key, v)
|
|
579
|
+
}, key)),
|
|
580
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(TextField, {
|
|
581
|
+
prefix: "dne",
|
|
582
|
+
id: "dne-debounce",
|
|
583
|
+
label: t("idleDebounceMs"),
|
|
584
|
+
hint: t("idleDebounceMsHint"),
|
|
585
|
+
value: draft.idleDebounceMs,
|
|
586
|
+
numeric: true,
|
|
587
|
+
disabled,
|
|
588
|
+
invalid: !isPositiveInt(draft.idleDebounceMs),
|
|
589
|
+
invalidLabel: t("invalidNumber"),
|
|
590
|
+
onEdit: (v) => onEdit("idleDebounceMs", v)
|
|
591
|
+
}),
|
|
592
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(TextField, {
|
|
593
|
+
prefix: "dne",
|
|
594
|
+
id: "dne-maxchars",
|
|
595
|
+
label: t("maxBodyChars"),
|
|
596
|
+
hint: t("maxBodyCharsHint"),
|
|
597
|
+
value: draft.maxBodyChars,
|
|
598
|
+
numeric: true,
|
|
599
|
+
disabled,
|
|
600
|
+
invalid: !isPositiveInt(draft.maxBodyChars) || Number(draft.maxBodyChars) < 200,
|
|
601
|
+
invalidLabel: t("invalidNumber"),
|
|
602
|
+
onEdit: (v) => onEdit("maxBodyChars", v)
|
|
603
|
+
}),
|
|
604
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(CheckRow, {
|
|
605
|
+
prefix: "dne",
|
|
606
|
+
id: "dne-dryrun",
|
|
607
|
+
label: t("dryRun"),
|
|
608
|
+
checked: draft.dryRun,
|
|
609
|
+
disabled,
|
|
610
|
+
onEdit: (v) => onEdit("dryRun", v)
|
|
611
|
+
})
|
|
612
|
+
] });
|
|
613
|
+
}
|
|
614
|
+
//#endregion
|
|
615
|
+
//#region src/client/card.tsx
|
|
616
|
+
/**
|
|
617
|
+
* 「邮件通知」配置卡片:经 injectPluginConfigCard 注册(插件页 plugins.row.config /
|
|
618
|
+
* plugins.bundle.config,view 分发 summary/page)。
|
|
619
|
+
* 外壳与基础控件走 @dsh-plus/shared/client 套件(CardChrome/TextField/CheckRow),
|
|
620
|
+
* 本文件只保留业务字段与保存/测试逻辑。交互对齐官方卡片:折叠/展开、
|
|
621
|
+
* staged draft、未保存标记、保存/放弃;另加「发送测试邮件」。
|
|
622
|
+
* 配置读写经 ctx.remote.settings 直连(0.1.2-alpha.1 起 connection.api.settings
|
|
623
|
+
* 已移除):value 为 schema 解析后的脱敏视图
|
|
624
|
+
* (smtp.pass 不出现,passConfigured 由 describe 的 secrets 探测),
|
|
625
|
+
* 保存经 settings.update 深合并(空 pass 剔除 = 保持不变)。
|
|
626
|
+
* @module notify-email/client/card
|
|
627
|
+
*/
|
|
628
|
+
/**
|
|
629
|
+
* passConfigured 探测:scope 快照不含 secrets,经 describe 的 secrets 列表判断;
|
|
630
|
+
* snapshot 变化(保存后 revision 推进)时刻意重探。
|
|
631
|
+
*/
|
|
632
|
+
function usePassConfigured(api, snapshot) {
|
|
489
633
|
const [passConfigured, setPassConfigured] = (0, react.useState)(false);
|
|
490
|
-
const [saving, setSaving] = (0, react.useState)(false);
|
|
491
|
-
const [testing, setTesting] = (0, react.useState)(false);
|
|
492
|
-
const [status, setStatus] = (0, react.useState)(IDLE_STATUS);
|
|
493
|
-
(0, react.useEffect)(() => {
|
|
494
|
-
if (value === void 0 || draft !== null) return;
|
|
495
|
-
setDraft(draftFromValue(value));
|
|
496
|
-
}, [value, draft]);
|
|
497
634
|
(0, react.useEffect)(() => {
|
|
498
635
|
let alive = true;
|
|
499
636
|
api.describe().then((view) => {
|
|
@@ -505,15 +642,74 @@ ${extra}
|
|
|
505
642
|
alive = false;
|
|
506
643
|
};
|
|
507
644
|
}, [api, snapshot]);
|
|
645
|
+
return passConfigured;
|
|
646
|
+
}
|
|
647
|
+
/** 卡片动作:发送测试、放弃、保存(保存受无效字段与只读限制)。 */
|
|
648
|
+
function cardActions(options) {
|
|
649
|
+
const { t } = options;
|
|
650
|
+
return [
|
|
651
|
+
{
|
|
652
|
+
key: "test",
|
|
653
|
+
label: t(options.testing ? "testing" : "test"),
|
|
654
|
+
disabled: options.testing,
|
|
655
|
+
onClick: options.onTest
|
|
656
|
+
},
|
|
657
|
+
{
|
|
658
|
+
key: "discard",
|
|
659
|
+
label: t("discard"),
|
|
660
|
+
disabled: !options.dirty || options.saving,
|
|
661
|
+
onClick: options.onDiscard
|
|
662
|
+
},
|
|
663
|
+
{
|
|
664
|
+
key: "save",
|
|
665
|
+
label: t(options.saving ? "saving" : "save"),
|
|
666
|
+
variant: "primary",
|
|
667
|
+
disabled: !options.dirty || options.invalid || options.saving || options.disabled,
|
|
668
|
+
onClick: options.onSave
|
|
669
|
+
}
|
|
670
|
+
];
|
|
671
|
+
}
|
|
672
|
+
/** 发送测试邮件:端点结果折算成状态行文案(不改草稿);finally 收尾由调用方给。 */
|
|
673
|
+
function sendTestMail(t, onStatus, onSettled) {
|
|
674
|
+
sendTest().then((result) => {
|
|
675
|
+
if (result.ok) {
|
|
676
|
+
onStatus({
|
|
677
|
+
kind: "ok",
|
|
678
|
+
text: result.detail === "dry-run" ? t("testDryRun") : t("testOk")
|
|
679
|
+
});
|
|
680
|
+
return;
|
|
681
|
+
}
|
|
682
|
+
const why = result.detail === "incomplete" ? t("incomplete") : result.detail;
|
|
683
|
+
onStatus({
|
|
684
|
+
kind: "error",
|
|
685
|
+
text: `${t("testFailed")}${why}`
|
|
686
|
+
});
|
|
687
|
+
}).catch((error) => {
|
|
688
|
+
onStatus({
|
|
689
|
+
kind: "error",
|
|
690
|
+
text: `${t("testFailed")}${error instanceof Error ? error.message : ""}`
|
|
691
|
+
});
|
|
692
|
+
}).finally(onSettled);
|
|
693
|
+
}
|
|
694
|
+
function NotifyEmailCard(props) {
|
|
695
|
+
const { t, scope, api } = props;
|
|
696
|
+
const snapshot = (0, react.useSyncExternalStore)((listener) => scope.subscribe(listener), () => scope.getSnapshot());
|
|
697
|
+
const value = snapshot.value;
|
|
698
|
+
const [draft, setDraft] = (0, react.useState)(null);
|
|
699
|
+
const [saving, setSaving] = (0, react.useState)(false);
|
|
700
|
+
const [testing, setTesting] = (0, react.useState)(false);
|
|
701
|
+
const [status, setStatus] = (0, react.useState)(IDLE_STATUS);
|
|
702
|
+
const passConfigured = usePassConfigured(api, snapshot);
|
|
703
|
+
(0, react.useEffect)(() => {
|
|
704
|
+
if (value === void 0 || draft !== null) return;
|
|
705
|
+
setDraft(draftFromValue(value));
|
|
706
|
+
}, [value, draft]);
|
|
508
707
|
const dirty = (0, react.useMemo)(() => value !== void 0 && draft !== null && JSON.stringify(toPatch(draft)) !== JSON.stringify(toPatch(draftFromValue(value))), [value, draft]);
|
|
509
708
|
const invalid = (0, react.useMemo)(() => draft !== null && (!isPositiveInt(draft.port) || !isPositiveInt(draft.idleDebounceMs) || !isPositiveInt(draft.maxBodyChars) || Number(draft.maxBodyChars) < 200), [draft]);
|
|
510
709
|
if (props.view === "summary") return t("summaryLine");
|
|
511
|
-
if (value === void 0 || draft === null) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
className: "dne-readOnly",
|
|
515
|
-
children: t("loading")
|
|
516
|
-
})
|
|
710
|
+
if (value === void 0 || draft === null) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CardLoading, {
|
|
711
|
+
prefix: "dne",
|
|
712
|
+
text: t("loading")
|
|
517
713
|
});
|
|
518
714
|
const edit = (key, editValue) => {
|
|
519
715
|
setDraft({
|
|
@@ -548,32 +744,13 @@ ${extra}
|
|
|
548
744
|
};
|
|
549
745
|
const onTest = () => {
|
|
550
746
|
setTesting(true);
|
|
551
|
-
|
|
552
|
-
if (result.ok) setStatus({
|
|
553
|
-
kind: "ok",
|
|
554
|
-
text: result.detail === "dry-run" ? t("testDryRun") : t("testOk")
|
|
555
|
-
});
|
|
556
|
-
else {
|
|
557
|
-
const why = result.detail === "incomplete" ? t("incomplete") : result.detail;
|
|
558
|
-
setStatus({
|
|
559
|
-
kind: "error",
|
|
560
|
-
text: `${t("testFailed")}${why}`
|
|
561
|
-
});
|
|
562
|
-
}
|
|
563
|
-
}).catch((error) => {
|
|
564
|
-
setStatus({
|
|
565
|
-
kind: "error",
|
|
566
|
-
text: `${t("testFailed")}${error instanceof Error ? error.message : ""}`
|
|
567
|
-
});
|
|
568
|
-
}).finally(() => setTesting(false));
|
|
747
|
+
sendTestMail(t, setStatus, () => setTesting(false));
|
|
569
748
|
};
|
|
570
749
|
const disabled = !snapshot.writable;
|
|
571
750
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(CardChrome, {
|
|
572
751
|
prefix: "dne",
|
|
573
752
|
title: t("title"),
|
|
574
753
|
description: t("description"),
|
|
575
|
-
open,
|
|
576
|
-
onToggle: setOpen,
|
|
577
754
|
statusBadge: {
|
|
578
755
|
text: t(draft.enabled ? "enabledOn" : "enabledOff"),
|
|
579
756
|
on: draft.enabled
|
|
@@ -582,30 +759,20 @@ ${extra}
|
|
|
582
759
|
dirtyLabel: t("unsaved"),
|
|
583
760
|
readOnlyNotice: disabled ? t("readOnly") : void 0,
|
|
584
761
|
status,
|
|
585
|
-
actions:
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
onClick: () => {
|
|
597
|
-
setDraft(draftFromValue(value));
|
|
598
|
-
setStatus(IDLE_STATUS);
|
|
599
|
-
}
|
|
762
|
+
actions: cardActions({
|
|
763
|
+
t,
|
|
764
|
+
dirty,
|
|
765
|
+
invalid,
|
|
766
|
+
saving,
|
|
767
|
+
testing,
|
|
768
|
+
disabled,
|
|
769
|
+
onTest,
|
|
770
|
+
onDiscard: () => {
|
|
771
|
+
setDraft(draftFromValue(value));
|
|
772
|
+
setStatus(IDLE_STATUS);
|
|
600
773
|
},
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
label: t(saving ? "saving" : "save"),
|
|
604
|
-
variant: "primary",
|
|
605
|
-
disabled: !dirty || invalid || saving || disabled,
|
|
606
|
-
onClick: onSave
|
|
607
|
-
}
|
|
608
|
-
],
|
|
774
|
+
onSave
|
|
775
|
+
}),
|
|
609
776
|
children: [
|
|
610
777
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(CheckRow, {
|
|
611
778
|
prefix: "dne",
|
|
@@ -615,119 +782,19 @@ ${extra}
|
|
|
615
782
|
disabled,
|
|
616
783
|
onEdit: (v) => edit("enabled", v)
|
|
617
784
|
}),
|
|
618
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
label: t("host"),
|
|
622
|
-
hint: t("hostHint"),
|
|
623
|
-
value: draft.host,
|
|
624
|
-
disabled,
|
|
625
|
-
onEdit: (v) => edit("host", v)
|
|
626
|
-
}),
|
|
627
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(TextField, {
|
|
628
|
-
prefix: "dne",
|
|
629
|
-
id: "dne-port",
|
|
630
|
-
label: t("port"),
|
|
631
|
-
hint: t("portHint"),
|
|
632
|
-
value: draft.port,
|
|
633
|
-
numeric: true,
|
|
634
|
-
disabled,
|
|
635
|
-
invalid: !isPositiveInt(draft.port),
|
|
636
|
-
invalidLabel: t("invalidNumber"),
|
|
637
|
-
onEdit: (v) => edit("port", v)
|
|
638
|
-
}),
|
|
639
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(CheckRow, {
|
|
640
|
-
prefix: "dne",
|
|
641
|
-
id: "dne-secure",
|
|
642
|
-
label: t("secure"),
|
|
643
|
-
checked: draft.secure,
|
|
644
|
-
disabled,
|
|
645
|
-
onEdit: (v) => edit("secure", v)
|
|
646
|
-
}),
|
|
647
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(TextField, {
|
|
648
|
-
prefix: "dne",
|
|
649
|
-
id: "dne-user",
|
|
650
|
-
label: t("user"),
|
|
651
|
-
hint: t("userHint"),
|
|
652
|
-
value: draft.user,
|
|
653
|
-
disabled,
|
|
654
|
-
onEdit: (v) => edit("user", v)
|
|
655
|
-
}),
|
|
656
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(TextField, {
|
|
657
|
-
prefix: "dne",
|
|
658
|
-
id: "dne-pass",
|
|
659
|
-
label: t("pass"),
|
|
660
|
-
hint: t("passHint"),
|
|
661
|
-
value: draft.pass,
|
|
662
|
-
password: true,
|
|
785
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SmtpFields, {
|
|
786
|
+
t,
|
|
787
|
+
draft,
|
|
663
788
|
disabled,
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
set: passConfigured
|
|
667
|
-
},
|
|
668
|
-
onEdit: (v) => edit("pass", v)
|
|
789
|
+
passConfigured,
|
|
790
|
+
onEdit: edit
|
|
669
791
|
}),
|
|
670
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
label: t("from"),
|
|
674
|
-
hint: t("fromHint"),
|
|
675
|
-
value: draft.from,
|
|
676
|
-
disabled,
|
|
677
|
-
onEdit: (v) => edit("from", v)
|
|
678
|
-
}),
|
|
679
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(TextField, {
|
|
680
|
-
prefix: "dne",
|
|
681
|
-
id: "dne-to",
|
|
682
|
-
label: t("to"),
|
|
683
|
-
hint: t("toHint"),
|
|
684
|
-
value: draft.toText,
|
|
685
|
-
disabled,
|
|
686
|
-
onEdit: (v) => edit("toText", v)
|
|
687
|
-
}),
|
|
688
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
689
|
-
className: "dne-groupLabel",
|
|
690
|
-
children: t("triggerGroup")
|
|
691
|
-
}),
|
|
692
|
-
TRIGGER_KEYS.map((key) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CheckRow, {
|
|
693
|
-
prefix: "dne",
|
|
694
|
-
id: `dne-${key}`,
|
|
695
|
-
label: t(key),
|
|
696
|
-
checked: draft.triggers[key],
|
|
697
|
-
disabled,
|
|
698
|
-
onEdit: (v) => editTrigger(key, v)
|
|
699
|
-
}, key)),
|
|
700
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(TextField, {
|
|
701
|
-
prefix: "dne",
|
|
702
|
-
id: "dne-debounce",
|
|
703
|
-
label: t("idleDebounceMs"),
|
|
704
|
-
hint: t("idleDebounceMsHint"),
|
|
705
|
-
value: draft.idleDebounceMs,
|
|
706
|
-
numeric: true,
|
|
707
|
-
disabled,
|
|
708
|
-
invalid: !isPositiveInt(draft.idleDebounceMs),
|
|
709
|
-
invalidLabel: t("invalidNumber"),
|
|
710
|
-
onEdit: (v) => edit("idleDebounceMs", v)
|
|
711
|
-
}),
|
|
712
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(TextField, {
|
|
713
|
-
prefix: "dne",
|
|
714
|
-
id: "dne-maxchars",
|
|
715
|
-
label: t("maxBodyChars"),
|
|
716
|
-
hint: t("maxBodyCharsHint"),
|
|
717
|
-
value: draft.maxBodyChars,
|
|
718
|
-
numeric: true,
|
|
719
|
-
disabled,
|
|
720
|
-
invalid: !isPositiveInt(draft.maxBodyChars) || Number(draft.maxBodyChars) < 200,
|
|
721
|
-
invalidLabel: t("invalidNumber"),
|
|
722
|
-
onEdit: (v) => edit("maxBodyChars", v)
|
|
723
|
-
}),
|
|
724
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(CheckRow, {
|
|
725
|
-
prefix: "dne",
|
|
726
|
-
id: "dne-dryrun",
|
|
727
|
-
label: t("dryRun"),
|
|
728
|
-
checked: draft.dryRun,
|
|
792
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(TriggerFields, {
|
|
793
|
+
t,
|
|
794
|
+
draft,
|
|
729
795
|
disabled,
|
|
730
|
-
onEdit:
|
|
796
|
+
onEdit: edit,
|
|
797
|
+
onTrigger: editTrigger
|
|
731
798
|
})
|
|
732
799
|
]
|
|
733
800
|
});
|