@puwenhui/dsh-email 0.2.2 → 0.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -38,6 +38,8 @@ dsh plugin --profile web add file:<本插件目录>
38
38
 
39
39
  **重要新邮件提醒**:后台每 60 秒轮询同步,新邮件按规则(发件人白名单 / 主题关键词 / 全部开关)判定,命中即**浏览器桌面通知 + 页内横幅**;水位去重,同一封只提醒一次。
40
40
 
41
+ **垃圾邮件处理**:「垃圾邮件」文件夹默认纳入同步(搜索可及,结果标注来源),**但永不触发提醒**——被邮箱误判的正常邮件能找回,又不被垃圾弹窗骚扰;移回收件箱等整理操作请用 Foxmail。
42
+
41
43
  **模型工具**(对话中由 AI 调用):
42
44
 
43
45
  | 工具 | 说明 |
package/USAGE.zh.md CHANGED
@@ -130,6 +130,9 @@ DSH 邮件助手让你**在对话里直接处理邮件**:查邮件、找附件
130
130
  **Q:老邮件的附件列表是空的?**
131
131
  插件只对近期同步过的邮件建立附件索引;翻不到就告诉我那封邮件的主题,我(管理员)可以重建索引。
132
132
 
133
+ **Q:怀疑邮件被扔进垃圾箱了,怎么找?**
134
+ 直接说:「**垃圾邮件里最近有什么**」「搜一下垃圾箱里张三发来的信」。垃圾箱的邮件插件会正常同步和搜索(结果会标注来自垃圾邮件),但**不会弹提醒**——翻到被误判的邮件后想移回收件箱,去 Foxmail 操作。
135
+
133
136
  **Q:没收到重要邮件提醒?**
134
137
  检查三处:① 设置 → 插件 → 邮件 卡里「开启重要新邮件提醒」是否打开;② 白名单/关键词是否填了;③ 页面右上角是否点过「开启桌面通知」。
135
138
 
package/lib/client.js CHANGED
@@ -74,7 +74,7 @@ window.__ModuleLoader__.load({ id: "@puwenhui/dsh-email", factory: (require) =>
74
74
  key: "folders",
75
75
  label: "同步文件夹",
76
76
  type: "array",
77
- placeholder: "INBOX, 已发送"
77
+ placeholder: "INBOX, 已发送, 垃圾邮件"
78
78
  },
79
79
  {
80
80
  key: "pollSeconds",
package/lib/client.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"client.js","names":["useSyncExternalStore","useState","useRef","IconChevronDownOutline14","h","useSyncExternalStore"],"sources":["../client-src/settings-card.tsx","../client-src/index.tsx"],"sourcesContent":["/**\n * 设置 → 插件 →「邮件」配置卡:邮箱连接与重要邮件提醒规则。\n * 交互与外观对齐宿主 PluginCard 模式:点击头部展开/收起、保存成功自动收起、\n * 未保存标记;颜色全部走宿主主题变量(--dsw-alias-*,跟随「通用设置」外观)。\n * 读写经宿主 settingsScope(revision fence);「已覆盖」标记与恢复默认基于\n * user 层字段存在性;密码字段不回显,输入新值才写入。\n */\nimport { useEffect, useRef, useState, useSyncExternalStore } from 'react'\nimport { IconChevronDownOutline14 } from '@deepseek-ai/dsh-client-ui-primitives'\n\n/** 结构类型:宿主 settingsScope.bind() 的返回(避免跨插件值导入) */\ninterface ScopeLike {\n getSnapshot(): { status: string; value: unknown; user: unknown; revision?: number }\n subscribe(listener: () => void): () => void\n mutate(ops: ReadonlyArray<{ op: 'set' | 'unset'; path: readonly string[]; value?: unknown }>, expectedRevision?: number): Promise<void>\n}\n\ninterface FieldDef {\n key: string\n label: string\n type: 'string' | 'number' | 'boolean' | 'array' | 'secret'\n placeholder?: string\n hint?: string\n}\n\n/** 字段清单(与宿主半边 MailSettings schema 保持同步) */\nconst FIELDS: FieldDef[] = [\n { key: 'user', label: '邮箱账号', type: 'string', placeholder: 'name@corp.com' },\n { key: 'pass', label: '邮箱密码', type: 'secret', hint: '留空表示不修改;未设置时使用环境变量' },\n { key: 'host', label: 'IMAP 收件服务器', type: 'string', placeholder: 'imap.263.net' },\n { key: 'port', label: 'IMAP 端口', type: 'number' },\n { key: 'tlsMode', label: '加密方式', type: 'string', placeholder: 'tls(可选 starttls / none)' },\n { key: 'smtpHost', label: 'SMTP 发件服务器', type: 'string', placeholder: 'smtp.263.net' },\n { key: 'smtpPort', label: 'SMTP 端口', type: 'number', hint: '263 为 25(无 SSL);465 自动 SSL' },\n { key: 'smtpUser', label: 'SMTP 账号', type: 'string', placeholder: '默认同邮箱账号' },\n { key: 'smtpPass', label: 'SMTP 密码', type: 'secret', hint: '默认同邮箱密码;留空表示不修改' },\n { key: 'folders', label: '同步文件夹', type: 'array', placeholder: 'INBOX, 已发送' },\n { key: 'pollSeconds', label: '同步间隔(秒)', type: 'number' },\n { key: 'backfillDays', label: '首次回填天数', type: 'number' },\n { key: 'notifyEnabled', label: '开启重要新邮件提醒', type: 'boolean' },\n { key: 'notifyAll', label: '所有新邮件都提醒', type: 'boolean', hint: '慎开,容易骚扰' },\n { key: 'notifyFrom', label: '重要发件人白名单', type: 'array', placeholder: 'leader@corp.com, 客户A' },\n { key: 'notifyKeywords', label: '主题关键词', type: 'array', placeholder: '审批, 紧急' },\n]\n\nexport function SettingsCard({ scope }: { scope: ScopeLike }): JSX.Element | null {\n // 裸传方法引用会丢 this(scope.store 变 undefined),必须绑定\n const subscribe = useSync(scope, s => s.subscribe.bind(s))\n const getSnapshot = useSync(scope, s => s.getSnapshot.bind(s))\n const snap = useSyncExternalStore(subscribe, getSnapshot)\n\n const [open, setOpen] = useState(false)\n const [draft, setDraft] = useState<Record<string, unknown> | null>(null)\n const [saving, setSaving] = useState(false)\n const [message, setMessage] = useState<string | null>(null)\n const [failed, setFailed] = useState(false)\n const saveStarted = useRef(false)\n\n const current = (snap.value ?? {}) as Record<string, unknown>\n const userLayer = (snap.user ?? {}) as Record<string, unknown>\n const shown = draft ?? current\n const dirty = draft !== null\n\n // 宿主 PluginCard 同款:保存成功(无失败、无未存编辑)后自动收起\n useEffect(() => {\n if (saving) { saveStarted.current = true; return }\n if (!saveStarted.current) return\n saveStarted.current = false\n if (!dirty && !failed) setOpen(false)\n }, [dirty, failed, saving])\n\n const edit = (key: string, value: unknown): void => {\n setMessage(null)\n setFailed(false)\n setDraft(prev => ({ ...(prev ?? { ...current }), [key]: value }))\n }\n\n const asText = (f: FieldDef): string => {\n const v = shown[f.key]\n if (f.type === 'array') return Array.isArray(v) ? v.join(', ') : String(v ?? '')\n return String(v ?? '')\n }\n\n const save = async (): Promise<void> => {\n if (draft === null) return\n setSaving(true)\n try {\n const ops: Array<{ op: 'set' | 'unset'; path: readonly string[]; value?: unknown }> = []\n for (const f of FIELDS) {\n const after = draft[f.key]\n if (after === undefined) continue // 未改动\n if (f.type === 'secret' && String(after) === '') continue // 密码留空 = 不修改\n const before = current[f.key]\n if (JSON.stringify(after) === JSON.stringify(before)) continue\n ops.push({ op: 'set', path: [f.key], value: after })\n }\n if (ops.length > 0) await scope.mutate(ops, snap.revision)\n setDraft(null)\n setFailed(false)\n setMessage(ops.length > 0 ? '已保存,即时生效' : '无更改')\n } catch (e) {\n setFailed(true)\n setMessage(`保存失败:${String((e as Error).message)}`)\n } finally {\n setSaving(false)\n }\n }\n\n const resetField = async (f: FieldDef): Promise<void> => {\n setSaving(true)\n try {\n await scope.mutate([{ op: 'unset', path: [f.key] }], snap.revision)\n if (draft !== null) {\n const next = { ...draft }\n delete next[f.key]\n setDraft(next)\n }\n setMessage(`已恢复默认:${f.label}`)\n } catch (e) {\n setFailed(true)\n setMessage(`恢复失败:${String((e as Error).message)}`)\n } finally {\n setSaving(false)\n }\n }\n\n if (snap.status !== 'ready') {\n if (snap.status === 'unavailable') return null // 宿主未暴露该命名空间时不留痕迹\n return <div style={{ ...cardStyle, padding: '12px 16px', color: 'var(--dsw-alias-label-tertiary)' }}>邮件配置加载中…</div>\n }\n\n return (\n <div style={cardStyle}>\n {/* 头部:点击展开/收起(宿主 PluginCard 同款交互) */}\n <button\n type=\"button\"\n aria-expanded={open}\n onClick={() => { setOpen(!open) }}\n style={{\n display: 'flex', alignItems: 'center', gap: '10px', width: '100%',\n background: 'transparent', border: 'none', padding: '2px 0',\n color: 'inherit', font: 'inherit', cursor: 'pointer', textAlign: 'left',\n }}\n >\n <span style={{ display: 'flex', flexDirection: 'column', gap: '2px', flex: '1 1 auto' }}>\n <span style={{ fontWeight: 600, fontSize: '14px', color: 'var(--dsw-alias-label-primary)' }}>邮件</span>\n <span style={{ fontSize: '12px', color: 'var(--dsw-alias-label-tertiary)' }}>邮箱连接(IMAP/SMTP)与重要新邮件提醒</span>\n </span>\n {dirty && <span style={{ fontSize: '11px', color: 'var(--dsw-alias-label-secondary)', border: '1px solid var(--dsw-alias-border-l2)', borderRadius: '4px', padding: '1px 6px' }}>未保存</span>}\n <IconChevronDownOutline14 style={{ transform: open ? 'rotate(180deg)' : 'none', transition: 'transform 0.15s', flex: '0 0 auto' }} />\n </button>\n\n {open && (\n <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '10px 14px', marginTop: '12px', paddingTop: '12px', borderTop: '1px solid var(--dsw-alias-border-l2)' }}>\n {FIELDS.map((f) => {\n const overridden = Object.hasOwn(userLayer, f.key)\n return (\n <label key={f.key} style={{ display: 'flex', flexDirection: 'column', gap: '4px', fontSize: '13px' }}>\n <span style={{ color: 'var(--dsw-alias-label-secondary)' }}>\n {f.label}\n {f.type === 'secret'\n ? (overridden && <span style={{ marginLeft: '6px', color: 'var(--dsw-alias-brand-primary)' }}>·已设置</span>)\n : (overridden && <span style={{ marginLeft: '6px', color: 'var(--dsw-alias-brand-primary)' }} title=\"用户已覆盖默认值\">·已覆盖</span>)}\n {overridden && (\n <button\n type=\"button\"\n disabled={saving}\n onClick={() => { void resetField(f) }}\n style={{ marginLeft: '8px', padding: '0 6px', border: '1px solid var(--dsw-alias-border-l2)', borderRadius: '4px', background: 'transparent', font: 'inherit', fontSize: '11px', color: 'var(--dsw-alias-label-secondary)', cursor: 'pointer' }}\n >恢复默认</button>\n )}\n </span>\n {f.type === 'boolean'\n ? <input\n type=\"checkbox\"\n checked={shown[f.key] === true}\n onChange={(e) => { edit(f.key, e.target.checked) }}\n style={{ width: '16px', height: '16px' }}\n />\n : <input\n type={f.type === 'secret' ? 'password' : 'text'}\n value={f.type === 'secret' ? String(draft?.[f.key] ?? '') : asText(f)}\n placeholder={f.placeholder ?? (f.type === 'secret' ? '输入新值(不回显)' : '')}\n onChange={(e) => {\n const raw = e.target.value\n if (f.type === 'number') edit(f.key, raw === '' ? undefined : Number(raw))\n else if (f.type === 'array') edit(f.key, raw === '' ? [] : raw.split(/[,,]/).map(s => s.trim()).filter(s => s !== ''))\n else edit(f.key, raw)\n }}\n style={{\n width: '100%', padding: '6px 10px', font: 'inherit', boxSizing: 'border-box',\n border: '1px solid var(--dsw-alias-border-l2)', borderRadius: '6px',\n background: 'var(--dsw-alias-bg-layer-2)', color: 'var(--dsw-alias-label-primary)',\n }}\n />}\n {f.hint !== undefined && <span style={{ fontSize: '11px', color: 'var(--dsw-alias-label-tertiary)' }}>{f.hint}</span>}\n </label>\n )\n })}\n <div style={{ gridColumn: '1 / -1', display: 'flex', alignItems: 'center', gap: '10px', marginTop: '4px' }}>\n <button\n type=\"button\"\n disabled={saving || !dirty}\n onClick={() => { void save() }}\n style={{\n padding: '6px 16px', borderRadius: '6px', font: 'inherit',\n border: '1px solid var(--dsw-alias-brand-primary)', background: 'var(--dsw-alias-brand-primary)',\n color: 'var(--dsw-alias-label-primary-foreground)', cursor: dirty ? 'pointer' : 'default',\n opacity: dirty ? 1 : 0.6,\n }}\n >{saving ? '保存中…' : '保存'}</button>\n {dirty && (\n <button\n type=\"button\"\n disabled={saving}\n onClick={() => { setDraft(null); setMessage(null); setFailed(false) }}\n style={{ padding: '6px 16px', border: '1px solid var(--dsw-alias-border-l2)', borderRadius: '6px', background: 'transparent', font: 'inherit', color: 'var(--dsw-alias-label-secondary)', cursor: 'pointer' }}\n >放弃更改</button>\n )}\n {message !== null && <span style={{ fontSize: '12px', color: failed ? 'var(--dsw-alias-label-error)' : 'var(--dsw-alias-label-secondary)' }}>{message}</span>}\n </div>\n </div>\n )}\n </div>\n )\n}\n\n/** 从 scope 派生稳定回调(绑定 this) */\nfunction useSync<T>(scope: ScopeLike, pick: (s: ScopeLike) => T): T {\n const [fn] = useState(() => pick(scope))\n return fn\n}\n\nconst cardStyle: React.CSSProperties = {\n border: '1px solid var(--dsw-alias-border-l2)',\n borderRadius: '10px',\n padding: '12px 16px',\n background: 'var(--dsw-alias-bg-layer-3)',\n font: '13px/1.5 system-ui, \"Segoe UI\", \"Microsoft YaHei\", sans-serif',\n color: 'var(--dsw-alias-label-primary)',\n}\n","/**\n * 浏览器半边:\n * 1) 设置 → 插件 →「邮件」配置卡(settings.plugin.item keyed 槽,读写经 settingsScope)\n * 2) 重要新邮件提醒:宿主 SSE(/dsh-email/notify)→ 桌面通知 + 页内右上横幅兜底\n */\nimport { createElement as h, useSyncExternalStore } from 'react'\nimport { SettingsCard } from './settings-card.tsx'\n\nexport const name = 'dsh-email'\nexport const inject = ['slots', 'locale', 'theme']\n\ninterface ImportantMail {\n subject: string\n from: string\n snippet: string\n tag: string\n}\n\ninterface Banner extends ImportantMail {\n key: number\n}\n\n// ── 模块级状态(横幅队列 + 通知权限快照),渲染器经 useSyncExternalStore 消费 ──\nlet banners: Banner[] = []\nlet permission: NotificationPermission | 'unsupported' =\n typeof Notification === 'undefined' ? 'unsupported' : Notification.permission\nconst listeners = new Set<() => void>()\n\nfunction emit(): void {\n for (const cb of listeners) cb()\n}\n\nconst store = {\n subscribe(cb: () => void): () => void {\n listeners.add(cb)\n return () => { listeners.delete(cb) }\n },\n getBanners(): Banner[] {\n return banners\n },\n getPermission(): NotificationPermission | 'unsupported' {\n return permission\n },\n}\n\nlet bannerSeq = 0\nfunction pushBanner(mail: ImportantMail): void {\n bannerSeq += 1\n const banner: Banner = { ...mail, key: bannerSeq }\n banners = [...banners, banner].slice(-4) // 最多同时 4 条\n emit()\n setTimeout(() => {\n banners = banners.filter(b => b.key !== banner.key)\n emit()\n }, 8000)\n}\n\nfunction showNotification(mail: ImportantMail): void {\n if (typeof Notification === 'undefined' || Notification.permission !== 'granted') return\n try {\n const n = new Notification(`新邮件:${mail.subject}`, {\n body: `${mail.from}\\n${mail.snippet}`,\n tag: mail.tag, // 同一封邮件只弹一次\n })\n n.onclick = () => { window.focus(); n.close() }\n } catch { /* 通知失败不影响横幅 */ }\n}\n\nfunction onImportantMails(mails: ImportantMail[]): void {\n for (const mail of mails) {\n showNotification(mail)\n pushBanner(mail)\n }\n}\n\n/** SSE 地址相对 document.baseURI 解析(反向代理子路径部署安全) */\nfunction notifyUrl(): string {\n return new URL('dsh-email/notify', document.baseURI).toString()\n}\n\nfunction connectSse(): () => void {\n if (typeof document === 'undefined') return () => {}\n const es = new EventSource(notifyUrl())\n es.addEventListener('important-mail', (ev) => {\n try { onImportantMails(JSON.parse((ev as MessageEvent).data as string) as ImportantMail[]) } catch { /* 忽略坏包 */ }\n })\n return () => es.close()\n}\n\nasync function requestNotifyPermission(): Promise<void> {\n if (typeof Notification === 'undefined') return\n permission = await Notification.requestPermission()\n emit()\n}\n\nexport function apply(ctx: {\n slots: {\n inject: (slot: string, register: () => () => void) => () => void\n register: (options: Record<string, unknown>, component: (props?: unknown) => unknown) => () => void\n }\n inject: (services: string[], callback: (scoped: unknown) => void) => () => void\n effect: (fn: () => unknown, label?: string) => () => void\n}): void {\n // 1) 配置卡:设置 → 插件 →「邮件」。\n // dshmarket 同款:在 settingsScope 服务可用的上下文里注册 keyed 卡\n // (不带 locale 字段——未注册词典的 locale 命名空间会让注册失败)。\n ctx.inject(['settingsScope'], (scopedCtx) => {\n const scoped = scopedCtx as {\n slots: typeof ctx.slots\n effect: typeof ctx.effect\n settingsScope: { bind(spec: { namespace: string }): unknown }\n }\n const scope = scoped.settingsScope.bind({ namespace: 'dsh-email' })\n scoped.effect(() => scoped.slots.inject('settings.plugin.item', () =>\n scoped.slots.register(\n { name: 'settings.plugin.item', key: 'dsh-email' },\n () => h(SettingsCard, { scope: scope as never }),\n )), 'dsh-email: settings card')\n })\n\n // 2) 重要邮件提醒:SSE → 桌面通知 + 页内横幅\n ctx.effect(() => {\n const disconnect = connectSse()\n const offOverlay = ctx.slots.inject('shell.overlay', () =>\n ctx.slots.register(\n { name: 'shell.overlay', id: 'dsh-email-notify', label: () => 'dsh-email' },\n () => h(NotifyLayer),\n ))\n return () => { disconnect(); offOverlay() }\n }, 'dsh-email: notify')\n}\n\nfunction NotifyLayer(): JSX.Element | null {\n const list = useSyncExternalStore(store.subscribe, store.getBanners)\n const perm = useSyncExternalStore(store.subscribe, store.getPermission)\n const showGuide = perm === 'default'\n if (list.length === 0 && !showGuide) return null\n return h('div', {\n style: {\n position: 'fixed', top: '14px', right: '14px', zIndex: 9999,\n display: 'flex', flexDirection: 'column', gap: '8px', maxWidth: '360px',\n pointerEvents: 'none',\n } satisfies React.CSSProperties,\n },\n showGuide && h('div', {\n style: {\n pointerEvents: 'auto', padding: '8px 12px', borderRadius: '8px',\n background: 'rgba(29,29,31,0.86)', color: '#fff',\n display: 'flex', alignItems: 'center', gap: '8px',\n font: '13px/1.4 system-ui, \"Segoe UI\", \"Microsoft YaHei\", sans-serif',\n } satisfies React.CSSProperties,\n },\n h('span', null, '邮件提醒'),\n h('button', {\n type: 'button',\n onClick: () => { void requestNotifyPermission() },\n style: {\n padding: '3px 10px', borderRadius: '6px', border: 'none',\n background: '#2563eb', color: '#fff', font: 'inherit', cursor: 'pointer',\n } satisfies React.CSSProperties,\n }, '开启桌面通知'),\n ),\n ...list.map(b => h('div', {\n key: b.key,\n style: {\n pointerEvents: 'auto', padding: '10px 14px', borderRadius: '10px',\n background: 'rgba(29,29,31,0.92)', color: '#fff',\n boxShadow: '0 4px 16px rgba(0,0,0,0.2)',\n font: '13px/1.45 system-ui, \"Segoe UI\", \"Microsoft YaHei\", sans-serif',\n } satisfies React.CSSProperties,\n },\n h('div', { style: { fontWeight: 600, marginBottom: '2px' } }, `✉️ ${b.subject}`),\n h('div', { style: { opacity: 0.85 } }, b.from),\n b.snippet !== '' && h('div', { style: { opacity: 0.65, marginTop: '2px' } }, b.snippet),\n )),\n )\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"client.js","names":["useSyncExternalStore","useState","useRef","IconChevronDownOutline14","h","useSyncExternalStore"],"sources":["../client-src/settings-card.tsx","../client-src/index.tsx"],"sourcesContent":["/**\n * 设置 → 插件 →「邮件」配置卡:邮箱连接与重要邮件提醒规则。\n * 交互与外观对齐宿主 PluginCard 模式:点击头部展开/收起、保存成功自动收起、\n * 未保存标记;颜色全部走宿主主题变量(--dsw-alias-*,跟随「通用设置」外观)。\n * 读写经宿主 settingsScope(revision fence);「已覆盖」标记与恢复默认基于\n * user 层字段存在性;密码字段不回显,输入新值才写入。\n */\nimport { useEffect, useRef, useState, useSyncExternalStore } from 'react'\nimport { IconChevronDownOutline14 } from '@deepseek-ai/dsh-client-ui-primitives'\n\n/** 结构类型:宿主 settingsScope.bind() 的返回(避免跨插件值导入) */\ninterface ScopeLike {\n getSnapshot(): { status: string; value: unknown; user: unknown; revision?: number }\n subscribe(listener: () => void): () => void\n mutate(ops: ReadonlyArray<{ op: 'set' | 'unset'; path: readonly string[]; value?: unknown }>, expectedRevision?: number): Promise<void>\n}\n\ninterface FieldDef {\n key: string\n label: string\n type: 'string' | 'number' | 'boolean' | 'array' | 'secret'\n placeholder?: string\n hint?: string\n}\n\n/** 字段清单(与宿主半边 MailSettings schema 保持同步) */\nconst FIELDS: FieldDef[] = [\n { key: 'user', label: '邮箱账号', type: 'string', placeholder: 'name@corp.com' },\n { key: 'pass', label: '邮箱密码', type: 'secret', hint: '留空表示不修改;未设置时使用环境变量' },\n { key: 'host', label: 'IMAP 收件服务器', type: 'string', placeholder: 'imap.263.net' },\n { key: 'port', label: 'IMAP 端口', type: 'number' },\n { key: 'tlsMode', label: '加密方式', type: 'string', placeholder: 'tls(可选 starttls / none)' },\n { key: 'smtpHost', label: 'SMTP 发件服务器', type: 'string', placeholder: 'smtp.263.net' },\n { key: 'smtpPort', label: 'SMTP 端口', type: 'number', hint: '263 为 25(无 SSL);465 自动 SSL' },\n { key: 'smtpUser', label: 'SMTP 账号', type: 'string', placeholder: '默认同邮箱账号' },\n { key: 'smtpPass', label: 'SMTP 密码', type: 'secret', hint: '默认同邮箱密码;留空表示不修改' },\n { key: 'folders', label: '同步文件夹', type: 'array', placeholder: 'INBOX, 已发送, 垃圾邮件' },\n { key: 'pollSeconds', label: '同步间隔(秒)', type: 'number' },\n { key: 'backfillDays', label: '首次回填天数', type: 'number' },\n { key: 'notifyEnabled', label: '开启重要新邮件提醒', type: 'boolean' },\n { key: 'notifyAll', label: '所有新邮件都提醒', type: 'boolean', hint: '慎开,容易骚扰' },\n { key: 'notifyFrom', label: '重要发件人白名单', type: 'array', placeholder: 'leader@corp.com, 客户A' },\n { key: 'notifyKeywords', label: '主题关键词', type: 'array', placeholder: '审批, 紧急' },\n]\n\nexport function SettingsCard({ scope }: { scope: ScopeLike }): JSX.Element | null {\n // 裸传方法引用会丢 this(scope.store 变 undefined),必须绑定\n const subscribe = useSync(scope, s => s.subscribe.bind(s))\n const getSnapshot = useSync(scope, s => s.getSnapshot.bind(s))\n const snap = useSyncExternalStore(subscribe, getSnapshot)\n\n const [open, setOpen] = useState(false)\n const [draft, setDraft] = useState<Record<string, unknown> | null>(null)\n const [saving, setSaving] = useState(false)\n const [message, setMessage] = useState<string | null>(null)\n const [failed, setFailed] = useState(false)\n const saveStarted = useRef(false)\n\n const current = (snap.value ?? {}) as Record<string, unknown>\n const userLayer = (snap.user ?? {}) as Record<string, unknown>\n const shown = draft ?? current\n const dirty = draft !== null\n\n // 宿主 PluginCard 同款:保存成功(无失败、无未存编辑)后自动收起\n useEffect(() => {\n if (saving) { saveStarted.current = true; return }\n if (!saveStarted.current) return\n saveStarted.current = false\n if (!dirty && !failed) setOpen(false)\n }, [dirty, failed, saving])\n\n const edit = (key: string, value: unknown): void => {\n setMessage(null)\n setFailed(false)\n setDraft(prev => ({ ...(prev ?? { ...current }), [key]: value }))\n }\n\n const asText = (f: FieldDef): string => {\n const v = shown[f.key]\n if (f.type === 'array') return Array.isArray(v) ? v.join(', ') : String(v ?? '')\n return String(v ?? '')\n }\n\n const save = async (): Promise<void> => {\n if (draft === null) return\n setSaving(true)\n try {\n const ops: Array<{ op: 'set' | 'unset'; path: readonly string[]; value?: unknown }> = []\n for (const f of FIELDS) {\n const after = draft[f.key]\n if (after === undefined) continue // 未改动\n if (f.type === 'secret' && String(after) === '') continue // 密码留空 = 不修改\n const before = current[f.key]\n if (JSON.stringify(after) === JSON.stringify(before)) continue\n ops.push({ op: 'set', path: [f.key], value: after })\n }\n if (ops.length > 0) await scope.mutate(ops, snap.revision)\n setDraft(null)\n setFailed(false)\n setMessage(ops.length > 0 ? '已保存,即时生效' : '无更改')\n } catch (e) {\n setFailed(true)\n setMessage(`保存失败:${String((e as Error).message)}`)\n } finally {\n setSaving(false)\n }\n }\n\n const resetField = async (f: FieldDef): Promise<void> => {\n setSaving(true)\n try {\n await scope.mutate([{ op: 'unset', path: [f.key] }], snap.revision)\n if (draft !== null) {\n const next = { ...draft }\n delete next[f.key]\n setDraft(next)\n }\n setMessage(`已恢复默认:${f.label}`)\n } catch (e) {\n setFailed(true)\n setMessage(`恢复失败:${String((e as Error).message)}`)\n } finally {\n setSaving(false)\n }\n }\n\n if (snap.status !== 'ready') {\n if (snap.status === 'unavailable') return null // 宿主未暴露该命名空间时不留痕迹\n return <div style={{ ...cardStyle, padding: '12px 16px', color: 'var(--dsw-alias-label-tertiary)' }}>邮件配置加载中…</div>\n }\n\n return (\n <div style={cardStyle}>\n {/* 头部:点击展开/收起(宿主 PluginCard 同款交互) */}\n <button\n type=\"button\"\n aria-expanded={open}\n onClick={() => { setOpen(!open) }}\n style={{\n display: 'flex', alignItems: 'center', gap: '10px', width: '100%',\n background: 'transparent', border: 'none', padding: '2px 0',\n color: 'inherit', font: 'inherit', cursor: 'pointer', textAlign: 'left',\n }}\n >\n <span style={{ display: 'flex', flexDirection: 'column', gap: '2px', flex: '1 1 auto' }}>\n <span style={{ fontWeight: 600, fontSize: '14px', color: 'var(--dsw-alias-label-primary)' }}>邮件</span>\n <span style={{ fontSize: '12px', color: 'var(--dsw-alias-label-tertiary)' }}>邮箱连接(IMAP/SMTP)与重要新邮件提醒</span>\n </span>\n {dirty && <span style={{ fontSize: '11px', color: 'var(--dsw-alias-label-secondary)', border: '1px solid var(--dsw-alias-border-l2)', borderRadius: '4px', padding: '1px 6px' }}>未保存</span>}\n <IconChevronDownOutline14 style={{ transform: open ? 'rotate(180deg)' : 'none', transition: 'transform 0.15s', flex: '0 0 auto' }} />\n </button>\n\n {open && (\n <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '10px 14px', marginTop: '12px', paddingTop: '12px', borderTop: '1px solid var(--dsw-alias-border-l2)' }}>\n {FIELDS.map((f) => {\n const overridden = Object.hasOwn(userLayer, f.key)\n return (\n <label key={f.key} style={{ display: 'flex', flexDirection: 'column', gap: '4px', fontSize: '13px' }}>\n <span style={{ color: 'var(--dsw-alias-label-secondary)' }}>\n {f.label}\n {f.type === 'secret'\n ? (overridden && <span style={{ marginLeft: '6px', color: 'var(--dsw-alias-brand-primary)' }}>·已设置</span>)\n : (overridden && <span style={{ marginLeft: '6px', color: 'var(--dsw-alias-brand-primary)' }} title=\"用户已覆盖默认值\">·已覆盖</span>)}\n {overridden && (\n <button\n type=\"button\"\n disabled={saving}\n onClick={() => { void resetField(f) }}\n style={{ marginLeft: '8px', padding: '0 6px', border: '1px solid var(--dsw-alias-border-l2)', borderRadius: '4px', background: 'transparent', font: 'inherit', fontSize: '11px', color: 'var(--dsw-alias-label-secondary)', cursor: 'pointer' }}\n >恢复默认</button>\n )}\n </span>\n {f.type === 'boolean'\n ? <input\n type=\"checkbox\"\n checked={shown[f.key] === true}\n onChange={(e) => { edit(f.key, e.target.checked) }}\n style={{ width: '16px', height: '16px' }}\n />\n : <input\n type={f.type === 'secret' ? 'password' : 'text'}\n value={f.type === 'secret' ? String(draft?.[f.key] ?? '') : asText(f)}\n placeholder={f.placeholder ?? (f.type === 'secret' ? '输入新值(不回显)' : '')}\n onChange={(e) => {\n const raw = e.target.value\n if (f.type === 'number') edit(f.key, raw === '' ? undefined : Number(raw))\n else if (f.type === 'array') edit(f.key, raw === '' ? [] : raw.split(/[,,]/).map(s => s.trim()).filter(s => s !== ''))\n else edit(f.key, raw)\n }}\n style={{\n width: '100%', padding: '6px 10px', font: 'inherit', boxSizing: 'border-box',\n border: '1px solid var(--dsw-alias-border-l2)', borderRadius: '6px',\n background: 'var(--dsw-alias-bg-layer-2)', color: 'var(--dsw-alias-label-primary)',\n }}\n />}\n {f.hint !== undefined && <span style={{ fontSize: '11px', color: 'var(--dsw-alias-label-tertiary)' }}>{f.hint}</span>}\n </label>\n )\n })}\n <div style={{ gridColumn: '1 / -1', display: 'flex', alignItems: 'center', gap: '10px', marginTop: '4px' }}>\n <button\n type=\"button\"\n disabled={saving || !dirty}\n onClick={() => { void save() }}\n style={{\n padding: '6px 16px', borderRadius: '6px', font: 'inherit',\n border: '1px solid var(--dsw-alias-brand-primary)', background: 'var(--dsw-alias-brand-primary)',\n color: 'var(--dsw-alias-label-primary-foreground)', cursor: dirty ? 'pointer' : 'default',\n opacity: dirty ? 1 : 0.6,\n }}\n >{saving ? '保存中…' : '保存'}</button>\n {dirty && (\n <button\n type=\"button\"\n disabled={saving}\n onClick={() => { setDraft(null); setMessage(null); setFailed(false) }}\n style={{ padding: '6px 16px', border: '1px solid var(--dsw-alias-border-l2)', borderRadius: '6px', background: 'transparent', font: 'inherit', color: 'var(--dsw-alias-label-secondary)', cursor: 'pointer' }}\n >放弃更改</button>\n )}\n {message !== null && <span style={{ fontSize: '12px', color: failed ? 'var(--dsw-alias-label-error)' : 'var(--dsw-alias-label-secondary)' }}>{message}</span>}\n </div>\n </div>\n )}\n </div>\n )\n}\n\n/** 从 scope 派生稳定回调(绑定 this) */\nfunction useSync<T>(scope: ScopeLike, pick: (s: ScopeLike) => T): T {\n const [fn] = useState(() => pick(scope))\n return fn\n}\n\nconst cardStyle: React.CSSProperties = {\n border: '1px solid var(--dsw-alias-border-l2)',\n borderRadius: '10px',\n padding: '12px 16px',\n background: 'var(--dsw-alias-bg-layer-3)',\n font: '13px/1.5 system-ui, \"Segoe UI\", \"Microsoft YaHei\", sans-serif',\n color: 'var(--dsw-alias-label-primary)',\n}\n","/**\n * 浏览器半边:\n * 1) 设置 → 插件 →「邮件」配置卡(settings.plugin.item keyed 槽,读写经 settingsScope)\n * 2) 重要新邮件提醒:宿主 SSE(/dsh-email/notify)→ 桌面通知 + 页内右上横幅兜底\n */\nimport { createElement as h, useSyncExternalStore } from 'react'\nimport { SettingsCard } from './settings-card.tsx'\n\nexport const name = 'dsh-email'\nexport const inject = ['slots', 'locale', 'theme']\n\ninterface ImportantMail {\n subject: string\n from: string\n snippet: string\n tag: string\n}\n\ninterface Banner extends ImportantMail {\n key: number\n}\n\n// ── 模块级状态(横幅队列 + 通知权限快照),渲染器经 useSyncExternalStore 消费 ──\nlet banners: Banner[] = []\nlet permission: NotificationPermission | 'unsupported' =\n typeof Notification === 'undefined' ? 'unsupported' : Notification.permission\nconst listeners = new Set<() => void>()\n\nfunction emit(): void {\n for (const cb of listeners) cb()\n}\n\nconst store = {\n subscribe(cb: () => void): () => void {\n listeners.add(cb)\n return () => { listeners.delete(cb) }\n },\n getBanners(): Banner[] {\n return banners\n },\n getPermission(): NotificationPermission | 'unsupported' {\n return permission\n },\n}\n\nlet bannerSeq = 0\nfunction pushBanner(mail: ImportantMail): void {\n bannerSeq += 1\n const banner: Banner = { ...mail, key: bannerSeq }\n banners = [...banners, banner].slice(-4) // 最多同时 4 条\n emit()\n setTimeout(() => {\n banners = banners.filter(b => b.key !== banner.key)\n emit()\n }, 8000)\n}\n\nfunction showNotification(mail: ImportantMail): void {\n if (typeof Notification === 'undefined' || Notification.permission !== 'granted') return\n try {\n const n = new Notification(`新邮件:${mail.subject}`, {\n body: `${mail.from}\\n${mail.snippet}`,\n tag: mail.tag, // 同一封邮件只弹一次\n })\n n.onclick = () => { window.focus(); n.close() }\n } catch { /* 通知失败不影响横幅 */ }\n}\n\nfunction onImportantMails(mails: ImportantMail[]): void {\n for (const mail of mails) {\n showNotification(mail)\n pushBanner(mail)\n }\n}\n\n/** SSE 地址相对 document.baseURI 解析(反向代理子路径部署安全) */\nfunction notifyUrl(): string {\n return new URL('dsh-email/notify', document.baseURI).toString()\n}\n\nfunction connectSse(): () => void {\n if (typeof document === 'undefined') return () => {}\n const es = new EventSource(notifyUrl())\n es.addEventListener('important-mail', (ev) => {\n try { onImportantMails(JSON.parse((ev as MessageEvent).data as string) as ImportantMail[]) } catch { /* 忽略坏包 */ }\n })\n return () => es.close()\n}\n\nasync function requestNotifyPermission(): Promise<void> {\n if (typeof Notification === 'undefined') return\n permission = await Notification.requestPermission()\n emit()\n}\n\nexport function apply(ctx: {\n slots: {\n inject: (slot: string, register: () => () => void) => () => void\n register: (options: Record<string, unknown>, component: (props?: unknown) => unknown) => () => void\n }\n inject: (services: string[], callback: (scoped: unknown) => void) => () => void\n effect: (fn: () => unknown, label?: string) => () => void\n}): void {\n // 1) 配置卡:设置 → 插件 →「邮件」。\n // dshmarket 同款:在 settingsScope 服务可用的上下文里注册 keyed 卡\n // (不带 locale 字段——未注册词典的 locale 命名空间会让注册失败)。\n ctx.inject(['settingsScope'], (scopedCtx) => {\n const scoped = scopedCtx as {\n slots: typeof ctx.slots\n effect: typeof ctx.effect\n settingsScope: { bind(spec: { namespace: string }): unknown }\n }\n const scope = scoped.settingsScope.bind({ namespace: 'dsh-email' })\n scoped.effect(() => scoped.slots.inject('settings.plugin.item', () =>\n scoped.slots.register(\n { name: 'settings.plugin.item', key: 'dsh-email' },\n () => h(SettingsCard, { scope: scope as never }),\n )), 'dsh-email: settings card')\n })\n\n // 2) 重要邮件提醒:SSE → 桌面通知 + 页内横幅\n ctx.effect(() => {\n const disconnect = connectSse()\n const offOverlay = ctx.slots.inject('shell.overlay', () =>\n ctx.slots.register(\n { name: 'shell.overlay', id: 'dsh-email-notify', label: () => 'dsh-email' },\n () => h(NotifyLayer),\n ))\n return () => { disconnect(); offOverlay() }\n }, 'dsh-email: notify')\n}\n\nfunction NotifyLayer(): JSX.Element | null {\n const list = useSyncExternalStore(store.subscribe, store.getBanners)\n const perm = useSyncExternalStore(store.subscribe, store.getPermission)\n const showGuide = perm === 'default'\n if (list.length === 0 && !showGuide) return null\n return h('div', {\n style: {\n position: 'fixed', top: '14px', right: '14px', zIndex: 9999,\n display: 'flex', flexDirection: 'column', gap: '8px', maxWidth: '360px',\n pointerEvents: 'none',\n } satisfies React.CSSProperties,\n },\n showGuide && h('div', {\n style: {\n pointerEvents: 'auto', padding: '8px 12px', borderRadius: '8px',\n background: 'rgba(29,29,31,0.86)', color: '#fff',\n display: 'flex', alignItems: 'center', gap: '8px',\n font: '13px/1.4 system-ui, \"Segoe UI\", \"Microsoft YaHei\", sans-serif',\n } satisfies React.CSSProperties,\n },\n h('span', null, '邮件提醒'),\n h('button', {\n type: 'button',\n onClick: () => { void requestNotifyPermission() },\n style: {\n padding: '3px 10px', borderRadius: '6px', border: 'none',\n background: '#2563eb', color: '#fff', font: 'inherit', cursor: 'pointer',\n } satisfies React.CSSProperties,\n }, '开启桌面通知'),\n ),\n ...list.map(b => h('div', {\n key: b.key,\n style: {\n pointerEvents: 'auto', padding: '10px 14px', borderRadius: '10px',\n background: 'rgba(29,29,31,0.92)', color: '#fff',\n boxShadow: '0 4px 16px rgba(0,0,0,0.2)',\n font: '13px/1.45 system-ui, \"Segoe UI\", \"Microsoft YaHei\", sans-serif',\n } satisfies React.CSSProperties,\n },\n h('div', { style: { fontWeight: 600, marginBottom: '2px' } }, `✉️ ${b.subject}`),\n h('div', { style: { opacity: 0.85 } }, b.from),\n b.snippet !== '' && h('div', { style: { opacity: 0.65, marginTop: '2px' } }, b.snippet),\n )),\n )\n}\n"],"mappings":"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"}
package/lib/plugin.mjs CHANGED
@@ -26,7 +26,7 @@ function resolveConfig(overrides = {}) {
26
26
  user,
27
27
  pass,
28
28
  dbPath: pick(overrides.dbPath, env.EMAIL_DB_PATH, local.dbPath, "mail.db"),
29
- folders: pick(overrides.folders, local.folders, ["INBOX", "\u5DF2\u53D1\u9001"]),
29
+ folders: pick(overrides.folders, local.folders, ["INBOX", "\u5DF2\u53D1\u9001", "\u5783\u573E\u90AE\u4EF6"]),
30
30
  backfillDays: Number(pick(overrides.backfillDays, env.EMAIL_BACKFILL_DAYS, local.backfillDays, 90)),
31
31
  pollSeconds: Number(pick(overrides.pollSeconds, env.EMAIL_POLL_SECONDS, 60)),
32
32
  maxSourceBytes: Number(pick(overrides.maxSourceBytes, env.EMAIL_MAX_SOURCE_BYTES, local.maxSourceBytes, 10 * 1024 * 1024)),
@@ -659,7 +659,7 @@ var EmailService = class {
659
659
  const kwList = Array.isArray(cfg.notifyKeywords) ? cfg.notifyKeywords : [];
660
660
  const important = [];
661
661
  for (const folder of this.cfg.folders) {
662
- if (folder === "已发送") continue;
662
+ if (folder === "已发送" || folder === "垃圾邮件") continue;
663
663
  const last = before[folder] ?? 0;
664
664
  const rows = this.store.db.prepare("SELECT mailbox, uid, subject, from_name, from_addr, snippet FROM messages WHERE mailbox=? AND uid>? ORDER BY uid").all(folder, last);
665
665
  for (const r of rows) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@puwenhui/dsh-email",
3
- "version": "0.2.2",
3
+ "version": "0.2.4",
4
4
  "description": "DSH 邮件助手插件:IMAP 同步 + 全文索引 + 会话归并 + 10 个模型工具 + 设置配置卡 + 重要新邮件桌面提醒",
5
5
  "repository": {
6
6
  "type": "git",