dsh-email 0.15.2 → 0.15.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,12 @@
4
4
 
5
5
  [历史英文记录](docs/CHANGELOG.en.md)
6
6
 
7
+ ## 0.15.3 (2026-10-04)
8
+
9
+ - 完成 [#21](https://github.com/STARDUSTLC666/dsh-email/issues/21):邮箱账号页新增「显示新邮件弹窗」开关,自动保存、重启后保留。关闭时隐藏当前卡片并停止自动邮件检查,不影响手动邮件工具。
10
+ - 重新开启建立新基线,不补弹关闭期间的旧邮件;跨页面保存的开关在下一次本地设置刷新时生效。关闭或卸载后,迟到的网络响应不能重新显示弹窗。
11
+ - 验收范围见 [0.15.3 通知开关检查](docs/validation/0.15.3.md)。
12
+
7
13
  ## 0.15.2 (2026-10-03)
8
14
 
9
15
  - 修复语言切换后设置菜单滞后一轮;英文草稿、账号、发送失败引导不再回退中文,保留结果不明时禁止盲目重发的提示。
package/README.en.md CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  [中文](README.md)
4
4
 
5
+ ![dsh-email whale girl plugin cover](https://raw.githubusercontent.com/STARDUSTLC666/dsh-email/main/assets/cover-whale-girl.png)
6
+
5
7
  Read, send, search and organize email from DSH with multiple accounts.
6
8
 
7
9
  [![npm](https://img.shields.io/npm/v/dsh-email)](https://www.npmjs.com/package/dsh-email) [![downloads](https://img.shields.io/npm/dm/dsh-email)](https://www.npmjs.com/package/dsh-email)
@@ -11,6 +13,7 @@ Read, send, search and organize email from DSH with multiple accounts.
11
13
  - Read, send, reply and forward using IMAP and SMTP.
12
14
  - Manage attachments, search results and mailbox organization.
13
15
  - Configure multiple accounts, Outlook OAuth2 and send approval.
16
+ - Turn new-mail popups off in settings; the preference survives restarts.
14
17
  - Prepare and edit local drafts, review To/Cc, content and attachment bytes, then confirm sending.
15
18
  - Manage trusted recipients per account and preview address, group and Cc matches.
16
19
 
package/README.md CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  [English](README.en.md)
4
4
 
5
+ ![dsh-email 鲸鱼娘插件封面](https://raw.githubusercontent.com/STARDUSTLC666/dsh-email/main/assets/cover-whale-girl.png)
6
+
5
7
  在 DSH 中收发、搜索和整理邮件,支持多个邮箱账号。
6
8
 
7
9
  [![npm](https://img.shields.io/npm/v/dsh-email)](https://www.npmjs.com/package/dsh-email) [![downloads](https://img.shields.io/npm/dm/dsh-email)](https://www.npmjs.com/package/dsh-email)
@@ -11,6 +13,7 @@
11
13
  - 通过 IMAP / SMTP 收发、回复和转发邮件。
12
14
  - 管理附件、搜索结果与邮箱整理操作。
13
15
  - 多账号设置,支持 Outlook OAuth2 和发信审批。
16
+ - 新邮件弹窗可在设置中关闭,保存后重启仍生效。
14
17
  - 准备和编辑本地草稿,核对发件人、To/Cc、正文与附件预览后再确认发送。
15
18
  - 按账号管理可信收件人,预览规则如何匹配地址、地址组和抄送。
16
19
 
package/docs/USAGE.en.md CHANGED
@@ -24,6 +24,8 @@ See the [draft workbench](DRAFTS.en.md) for editable drafts and per-account trus
24
24
 
25
25
  Once an account is configured, a "whale-girl courier" popup lives in the bottom-right corner of the main UI: it checks for new mail every 30 seconds and shows a card (sender + subject) when something arrives, auto-dismissing after 12 seconds. The popup shares the cursor logic with `email_watch` but keeps its own counter, so they never consume each other's mail.
26
26
 
27
+ In Settings → Email → Accounts, turn off **Show new-mail popups** to hide the current popup and stop automatic mail checks. This setting saves automatically and survives a restart; it defaults to on. Re-enabling first seeds a fresh baseline, without replaying mail from the disabled period. Checks pause while the page is hidden. A change saved in another page takes effect on the next local-settings refresh. Conversation tools, including `email_watch`, remain available.
28
+
27
29
  The popup artwork prefers the locally installed [dsh-deep-whale](https://github.com/Small-tailqwq/dsh-deep-whale) whale-girl skin assets (**never bundled** — read at runtime from your own installation): the artwork is a derivative of the original whale-girl character by [上善](https://www.pixiv.net/users/62155430) (skin by Small-tailqwq), published under CC BY-NC-SA 4.0 (Attribution-NonCommercial-ShareAlike); the popup shows the full attribution chain. Without the skin, a bundled community whale-girl artwork is used (copyright stays with the original author, personal non-commercial use only; removed on request via issue).
28
30
 
29
31
  Example:
package/docs/USAGE.md CHANGED
@@ -24,6 +24,8 @@
24
24
 
25
25
  配置好账号后,主界面右下角会出现「鲸鱼娘递信」小弹窗:每 30 秒检查一次新邮件,有新邮件时弹出卡片(发件人 + 主题),12 秒自动消失。弹窗与 `email_watch` 工具共用同一套游标逻辑但各自独立计数,互不抢占。
26
26
 
27
+ 在「设置 → 邮件 → 邮箱账号」取消勾选「显示新邮件弹窗」,立即隐藏当前弹窗并停止自动检查;设置会自动保存,重启后仍生效。默认开启。再次开启时先建立新的基线,不补弹关闭期间的旧邮件。页面隐藏时暂停检查;另一个页面保存的开关在下一次本地设置刷新时生效。对话中的 `email_watch` 等邮件工具照常可用。
28
+
27
29
  弹窗形象优先使用本地安装的 [dsh-deep-whale](https://github.com/Small-tailqwq/dsh-deep-whale) 鲸鱼娘皮肤素材(**不打包分发**,运行时从你自己的安装目录读取):该素材为一创 [上善](https://www.pixiv.net/users/62155430) 鲸鱼娘形象的衍生创作(二创 Small-tailqwq),以 CC BY-NC-SA 4.0(署名-非商业性使用-相同方式共享)发布,弹窗内附完整署名链。未安装皮肤时使用内置的社区鲸鱼娘形象(版权归原作者,仅供个人非商业使用;如有异议请提 Issue,会立即移除)。
28
30
 
29
31
  示例对话:
@@ -1,5 +1,7 @@
1
1
  # dsh-email 验证记录
2
2
 
3
+ [0.15.3 通知开关验收](validation/0.15.3.md)。
4
+
3
5
  [0.15.2 中英文界面验收](validation-language-2026-10-03.md)。
4
6
 
5
7
  ## 0.15.1(2026-10-03)
@@ -0,0 +1,7 @@
1
+ # 0.15.3 通知开关验收 · 2026-10-04
2
+
3
+ Windows / Node 24.16.0:334 项测试全部通过。新增回归覆盖默认开启、持久化、不重建连接池、关闭时停止自动检查、跨页面重新开启、静默基线,以及忽略取消信号的迟到响应在关闭或卸载后不能复活弹窗。
4
+
5
+ 在官方源码构建的 DSH 0.2.0-rc.2(639ed01539)独立 profile 中操作了中文和英文设置页,关闭开关、自动保存、键盘 Space 重新开启及页面重新加载。主界面实际显示了合成新邮件弹窗,再从邮件设置关闭开关,卡片被移除。设置写入独立配置文件,测试账号和合成新邮件不含真实邮箱凭据。
6
+
7
+ 网络和通知测试不发送邮件,也不改变对话中的邮件工具权限。真实邮箱登录、SMTP 投递及原生桌面窗口未在本轮验收;网页通过不能替代原生桌面验收。
package/lib/client.js CHANGED
@@ -32,7 +32,7 @@ function emailError(error) {
32
32
  /** 「编辑即保存」的防抖窗口:连续打字只落一次盘。 */
33
33
  const SAVE_DEBOUNCE_MS = 800;
34
34
 
35
- async function api(action, payload) {
35
+ async function api(action, payload, signal) {
36
36
  const init = action === undefined
37
37
  ? { credentials: "same-origin" }
38
38
  : {
@@ -41,7 +41,7 @@ async function api(action, payload) {
41
41
  headers: { "Content-Type": "application/json" },
42
42
  body: JSON.stringify(Object.assign({ action }, payload)),
43
43
  };
44
- const res = await fetch(ROUTE, init);
44
+ const res = await fetch(ROUTE, { ...init, signal });
45
45
  const body = await res.json();
46
46
  if (!res.ok || !body.ok) {
47
47
  const error = new Error((body && body.error && body.error.message) || ("request failed " + res.status));
@@ -138,6 +138,7 @@ const EMPTY = {
138
138
  password: "",
139
139
  inboxFolder: "INBOX",
140
140
  sendApproval: true,
141
+ newMailPopup: true,
141
142
  downloadDir: "",
142
143
  accountsYaml: "",
143
144
  serverPresets: "",
@@ -290,7 +291,7 @@ var UI = {
290
291
  "global.sendApproval": "发信前弹确认(强烈建议保留;Full Access 模式下会被自动拒绝)",
291
292
  "global.downloadDirLabel": "附件下载目录(默认保存在当前工作区)",
292
293
  "global.downloadDirPlaceholder": "留空使用默认",
293
- "global.hint": "以上两项对所有账号统一生效。",
294
+ "global.hint": "以上设置对所有账号统一生效。",
294
295
 
295
296
  // 自动保存状态条
296
297
  "status.saved": "已保存",
@@ -471,7 +472,7 @@ var UI = {
471
472
  "global.sendApproval": "Ask for confirmation before sending (strongly recommended; auto-denied under Full Access)",
472
473
  "global.downloadDirLabel": "Attachment download directory (defaults to the current workspace)",
473
474
  "global.downloadDirPlaceholder": "Leave empty for the default",
474
- "global.hint": "These two settings apply to every account.",
475
+ "global.hint": "These settings apply to every account.",
475
476
 
476
477
  // 自动保存状态条
477
478
  "status.saved": "Saved",
@@ -2300,6 +2301,11 @@ function EmailSettingsSection() {
2300
2301
  const [tab, setTab] = useState(emailActiveTab);
2301
2302
  useEffect(() => { emailActiveTab = tab; }, [tab]);
2302
2303
  const [draft, setDraft] = useState(null);
2304
+ useEffect(() => {
2305
+ if (draft === null) return undefined;
2306
+ updateWhalePreference?.(draft.newMailPopup !== false);
2307
+ return () => updateWhalePreference?.(null);
2308
+ }, [draft?.newMailPopup]);
2303
2309
  const [snapshot, setSnapshot] = useState(undefined);
2304
2310
  const [busy, setBusy] = useState(false);
2305
2311
  const [error, setError] = useState("");
@@ -2592,6 +2598,16 @@ function EmailSettingsSection() {
2592
2598
  // 全局设置:发信确认和下载目录对所有账号一视同仁,不属于任何一张卡片,
2593
2599
  // 所以摆在卡片列表上方,而不是塞进某一张卡里。
2594
2600
  h("div", { className: "dshe-global" }, [
2601
+ h("label", { className: "dshe-check" }, [
2602
+ h("input", {
2603
+ type: "checkbox",
2604
+ checked: draft.newMailPopup !== false,
2605
+ disabled: snapshot?.writable === false,
2606
+ onChange: (e) => update({ newMailPopup: e.target.checked }),
2607
+ }),
2608
+ dt("显示新邮件弹窗", "Show new-mail popups"),
2609
+ ]),
2610
+ h("div", { className: "dshe-hint" }, dt("关闭后立即隐藏弹窗并停止自动检查,不影响对话中手动查看邮件。重新开启时从当前邮件开始提醒。", "Turning this off hides the popup and stops automatic mail checks. You can still check mail in a conversation. Turning it back on starts with a fresh baseline.")),
2595
2611
  h("label", { className: "dshe-check" }, [
2596
2612
  h("input", {
2597
2613
  type: "checkbox",
@@ -2641,6 +2657,7 @@ function EmailSettingsSection() {
2641
2657
  // when newCount > 0. The artwork URL/credit come from the settings snapshot
2642
2658
  // (skin artwork is served from the user's local dsh-deep-whale install with
2643
2659
  // its CC BY-NC-SA attribution chain; otherwise a built-in fallback is used).
2660
+ let updateWhalePreference = null;
2644
2661
  function startWhaleWidget() {
2645
2662
  if (document.querySelector("#dsh-email-whale-root")) return () => {};
2646
2663
  const root = document.createElement("div");
@@ -2651,17 +2668,26 @@ function startWhaleWidget() {
2651
2668
  let hideTimer = 0;
2652
2669
  let whaleUrl = "";
2653
2670
  let whaleCredit = "";
2671
+ let enabled = true;
2672
+ let localPreference = null;
2673
+ let disposed = false;
2674
+ let running = false;
2675
+ let generation = 0;
2676
+ let seedBaseline = true;
2677
+ let watchAbort = null;
2654
2678
  /** 当前弹窗的数据。语言切了就靠它原样重画一遍,无需再拉一次接口。 */
2655
2679
  let current = null;
2656
2680
 
2657
2681
  const closePopup = () => {
2658
2682
  if (hideTimer) { clearTimeout(hideTimer); hideTimer = 0; }
2659
2683
  root.replaceChildren();
2684
+ current = null;
2660
2685
  };
2661
2686
 
2662
2687
  const showPopup = (value) => {
2663
- current = value;
2664
2688
  closePopup();
2689
+ if (disposed || !enabled) return;
2690
+ current = value;
2665
2691
  const card = document.createElement("div");
2666
2692
  card.className = "dshe-whale-card";
2667
2693
  if (whaleUrl) {
@@ -2709,46 +2735,63 @@ function startWhaleWidget() {
2709
2735
  };
2710
2736
 
2711
2737
  // 轮询策略:页面不可见时完全不打扰邮箱服务器;失败指数退避(上限 10 分钟),
2712
- // 成功立刻回到 30s;快照(皮肤 / 账号列表)变化很慢,最多 10 分钟刷新一次。
2738
+ // 成功立刻回到 30s;本地设置快照同样刷新,以便跨页面的通知开关生效。
2713
2739
  const POLL_MS = 30000;
2714
- const SNAPSHOT_MS = 600000;
2715
2740
  const MAX_BACKOFF_MS = 600000;
2716
2741
  let failures = 0;
2717
- let lastSnapshot = 0;
2718
- let hasAccounts = true;
2742
+ const setEnabled = (value) => {
2743
+ if (value === enabled) return;
2744
+ enabled = value;
2745
+ generation++;
2746
+ seedBaseline = true;
2747
+ watchAbort?.abort();
2748
+ if (!enabled) closePopup();
2749
+ };
2750
+ updateWhalePreference = value => {
2751
+ localPreference = value;
2752
+ if (value !== null) setEnabled(value);
2753
+ };
2719
2754
 
2720
2755
  const tick = async () => {
2721
- if (document.hidden) return;
2756
+ if (disposed || document.hidden || running) return;
2757
+ running = true;
2722
2758
  try {
2723
- if (!hasAccounts || Date.now() - lastSnapshot > SNAPSHOT_MS) {
2724
- const snap = await api();
2725
- lastSnapshot = Date.now();
2726
- if (snap && snap.whale) {
2727
- whaleUrl = (snap.whale.url || "").replace(/^\/_dsh\//, "_dsh/");
2728
- whaleCredit = snap.whale.credit || "";
2729
- }
2730
- hasAccounts = !!(snap && snap.accounts && snap.accounts.length > 0);
2759
+ // Refresh the small local snapshot even when disabled so another tab's
2760
+ // saved preference takes effect. This does not contact the mail server.
2761
+ const snap = await api();
2762
+ if (disposed) return;
2763
+ setEnabled(localPreference ?? (snap?.settings?.value?.newMailPopup !== false));
2764
+ if (snap && snap.whale) {
2765
+ whaleUrl = (snap.whale.url || "").replace(/^\/_dsh\//, "_dsh/");
2766
+ whaleCredit = snap.whale.credit || "";
2731
2767
  }
2732
- if (!hasAccounts) return;
2733
- const value = await api("watch", { limit: 5 });
2734
- if (value && value.newCount > 0) showPopup(value);
2768
+ if (!enabled || !snap?.accounts?.length || document.hidden) { failures = 0; return; }
2769
+ const version = generation;
2770
+ watchAbort = new AbortController();
2771
+ const value = await api("watch", { limit: 5 }, watchAbort.signal);
2772
+ if (disposed || !enabled || version !== generation || document.hidden) return;
2773
+ if (!seedBaseline && value && value.newCount > 0) showPopup(value);
2774
+ seedBaseline = false;
2735
2775
  failures = 0;
2736
2776
  } catch (e) {
2737
2777
  // Not configured or transient error: stay silent and back off.
2738
2778
  failures = Math.min(failures + 1, 8);
2779
+ } finally {
2780
+ running = false;
2781
+ watchAbort = null;
2739
2782
  }
2740
2783
  };
2741
2784
 
2742
2785
  const schedule = () => {
2743
2786
  clearTimeout(pollTimer);
2744
- if (document.hidden) return; // 回到前台时 onVisibility 会立刻补一次
2787
+ if (disposed || document.hidden) return;
2745
2788
  const delay = failures === 0 ? POLL_MS : Math.min(POLL_MS * Math.pow(2, failures - 1), MAX_BACKOFF_MS);
2746
2789
  pollTimer = setTimeout(() => { void tick().then(schedule); }, delay);
2747
2790
  };
2748
2791
 
2749
2792
  const onVisibility = () => {
2750
2793
  clearTimeout(pollTimer);
2751
- if (document.hidden) return;
2794
+ if (disposed || document.hidden) { watchAbort?.abort(); closePopup(); return; }
2752
2795
  failures = 0;
2753
2796
  void tick().then(schedule);
2754
2797
  };
@@ -2757,10 +2800,14 @@ function startWhaleWidget() {
2757
2800
  // 弹窗是命令式 DOM,没有 React 那层重渲染:语言切换后自己重画一次(开着的才重画)。
2758
2801
  relocalize = () => { if (current !== null && root.childElementCount > 0) showPopup(current); };
2759
2802
  return () => {
2803
+ disposed = true;
2804
+ generation++;
2805
+ watchAbort?.abort();
2760
2806
  clearTimeout(pollTimer);
2761
2807
  document.removeEventListener("visibilitychange", onVisibility);
2762
2808
  if (hideTimer) clearTimeout(hideTimer);
2763
2809
  relocalize = null;
2810
+ updateWhalePreference = null;
2764
2811
  root.remove();
2765
2812
  };
2766
2813
  }
package/lib/config.d.ts CHANGED
@@ -108,6 +108,8 @@ export interface AccountConfig {
108
108
  inboxFolder?: string;
109
109
  }
110
110
  export interface EmailConfig extends AccountConfig {
111
+ /** Show browser new-mail notifications. UI policy only; default true. */
112
+ newMailPopup?: boolean;
111
113
  /** Per-account recipient rules, independent of IMAP/SMTP connection settings. */
112
114
  trustedRecipientsYaml?: string;
113
115
  /** Ask the user for approval before email_send. Default true. */
@@ -12,6 +12,7 @@ export const Config = z.object({
12
12
  clientId: z.string().volatile(), authKind: z.union(['password', 'oauth2']).volatile(),
13
13
  imap: endpoint().volatile(), smtp: endpoint().volatile(),
14
14
  inboxFolder: z.string().volatile(), sendApproval: z.boolean().volatile(),
15
+ newMailPopup: z.boolean().volatile(),
15
16
  maxBodyChars: z.number().volatile(), downloadDir: z.string().volatile(),
16
17
  accounts: z.dict(z.any()).role('secret').volatile(), defaultAccount: z.string().volatile(),
17
18
  accountsYaml: z.string().role('secret').volatile(), serverPresets: z.string().volatile(),
package/lib/settings.d.ts CHANGED
@@ -12,6 +12,7 @@ export declare const EmailSettingsSchema: import("@deepseek-ai/schemastery").def
12
12
  password: import("@deepseek-ai/schemastery").default<string, string, Mode extends "volatile" | "volatile-defined" ? "volatile-defined" : "defined">;
13
13
  inboxFolder: import("@deepseek-ai/schemastery").default<string, string, Mode extends "volatile" | "volatile-defined" ? "volatile-defined" : "defined">;
14
14
  sendApproval: import("@deepseek-ai/schemastery").default<boolean, boolean, Mode extends "volatile" | "volatile-defined" ? "volatile-defined" : "defined">;
15
+ newMailPopup: import("@deepseek-ai/schemastery").default<boolean, boolean, Mode extends "volatile" | "volatile-defined" ? "volatile-defined" : "defined">;
15
16
  trustedRecipientsYaml: import("@deepseek-ai/schemastery").default<string, string, Mode extends "volatile" | "volatile-defined" ? "volatile-defined" : "defined">;
16
17
  maxBodyChars: import("@deepseek-ai/schemastery").default<number, number, Mode extends "volatile" | "volatile-defined" ? "volatile-defined" : "defined">;
17
18
  downloadDir: import("@deepseek-ai/schemastery").default<string, string, Mode extends "volatile" | "volatile-defined" ? "volatile-defined" : "defined">;
@@ -41,6 +42,7 @@ export declare const EmailSettingsSchema: import("@deepseek-ai/schemastery").def
41
42
  password: import("@deepseek-ai/schemastery").default<string, string, Mode extends "volatile" | "volatile-defined" ? "volatile-defined" : "defined">;
42
43
  inboxFolder: import("@deepseek-ai/schemastery").default<string, string, Mode extends "volatile" | "volatile-defined" ? "volatile-defined" : "defined">;
43
44
  sendApproval: import("@deepseek-ai/schemastery").default<boolean, boolean, Mode extends "volatile" | "volatile-defined" ? "volatile-defined" : "defined">;
45
+ newMailPopup: import("@deepseek-ai/schemastery").default<boolean, boolean, Mode extends "volatile" | "volatile-defined" ? "volatile-defined" : "defined">;
44
46
  trustedRecipientsYaml: import("@deepseek-ai/schemastery").default<string, string, Mode extends "volatile" | "volatile-defined" ? "volatile-defined" : "defined">;
45
47
  maxBodyChars: import("@deepseek-ai/schemastery").default<number, number, Mode extends "volatile" | "volatile-defined" ? "volatile-defined" : "defined">;
46
48
  downloadDir: import("@deepseek-ai/schemastery").default<string, string, Mode extends "volatile" | "volatile-defined" ? "volatile-defined" : "defined">;
@@ -71,6 +73,7 @@ export interface EmailSettingsValue {
71
73
  password: string;
72
74
  inboxFolder: string;
73
75
  sendApproval: boolean;
76
+ newMailPopup?: boolean;
74
77
  trustedRecipientsYaml?: string;
75
78
  maxBodyChars: number;
76
79
  downloadDir: string;
package/lib/settings.js CHANGED
@@ -14,6 +14,7 @@ export const EmailSettingsSchema = z.object({
14
14
  password: z.string().role('secret').default(''),
15
15
  inboxFolder: z.string().default('INBOX'),
16
16
  sendApproval: z.boolean().default(true),
17
+ newMailPopup: z.boolean().default(true),
17
18
  trustedRecipientsYaml: z.string().default(''),
18
19
  maxBodyChars: z.number().default(20000),
19
20
  downloadDir: z.string().default(''),
@@ -41,6 +42,7 @@ export function toSettingsBase(config) {
41
42
  ...(config.password !== undefined && config.password !== '' ? { password: config.password } : {}),
42
43
  ...(config.inboxFolder !== undefined && config.inboxFolder !== '' ? { inboxFolder: config.inboxFolder } : {}),
43
44
  ...(config.sendApproval !== undefined ? { sendApproval: config.sendApproval } : {}),
45
+ ...(config.newMailPopup !== undefined ? { newMailPopup: config.newMailPopup } : {}),
44
46
  ...(config.trustedRecipientsYaml !== undefined ? { trustedRecipientsYaml: config.trustedRecipientsYaml } : {}),
45
47
  ...(config.maxBodyChars !== undefined ? { maxBodyChars: config.maxBodyChars } : {}),
46
48
  ...(config.downloadDir !== undefined && config.downloadDir !== '' ? { downloadDir: config.downloadDir } : {}),
@@ -203,6 +205,9 @@ function describeValue(value) {
203
205
  */
204
206
  export function validateSettingsValue(value, extraProviders = []) {
205
207
  const draft = (value ?? {});
208
+ if (draft.newMailPopup !== undefined && typeof draft.newMailPopup !== 'boolean') {
209
+ throw new Error('新邮件弹窗开关必须为布尔值');
210
+ }
206
211
  if (draft.trustedRecipientsYaml !== undefined) {
207
212
  if (typeof draft.trustedRecipientsYaml !== 'string')
208
213
  throw new Error('可信收件人规则必须为文本');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-email",
3
- "version": "0.15.2",
3
+ "version": "0.15.3",
4
4
  "description": "在 DSH 中收发、搜索和整理邮件,支持多个邮箱账号。",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",