dsh-mail — 邮件(DSH Web 插件)
仿 Foxmail 的多账号邮件客户端,作为 DeepSeek Harness Web 的左侧栏入口 + 主面板运行。
Host 半边用纯 Node(node:net / node:tls)实现 IMAP 与 SMTP,客户端半边是原生 React
单文件面板,零第三方依赖,所有邮件与账号数据只保存在本机。
- 侧栏入口:邮件图标(id
mail,order 35),主面板 keymail - 数据目录:
$DSH_HOME/dsh-mail/config.json(0600) - 附件默认目录:
~/Documents/DSH 邮件附件(可在设置中修改)
功能
1. 邮箱配置
- 多邮箱账号:显示名、邮箱地址、发件人名称、密码、IMAP/SMTP 主机、端口、SSL / STARTTLS、 自签证书开关、已发送/已删除/垃圾箱/草稿/存档等文件夹(可自动探测)。
- 服务商快速配置(v1.2):内置 Gmail、QQ 邮箱、163 / 126、腾讯企业邮、阿里企业邮与自定义预设;
选择预设后自动填入收件/发件服务器、端口、TLS 与文件夹映射,输入邮箱地址时也会按域名自动推荐预设。
Gmail 预设附带应用专用密码引导与官方设置入口,并预置
[Gmail]/Sent Mail · [Gmail]/Trash · [Gmail]/Drafts。 - 连接测试:IMAP 登录并列出文件夹、SMTP 登录(不发送测试邮件),错误分段展示。
- 每账号自定义加载条数:最近 50 封 / 最近 100 封 / 全部历史;邮件列表每页固定 20 条, 底部「加载更多」逐页追加,共 N 封与当前加载上限一目了然。
2. 邮箱功能
- 左侧账号/文件夹树(多账号展开收起),中间邮件列表,右侧阅读窗格(Foxmail 三栏布局)。
- 邮件阅读弹窗:双击邮件行(或阅读窗格「在新窗口打开」)弹出独立阅读窗口,可按住标题栏拖动、 拖拽右/下/右下角缩放,窗口大小与位置自动记忆。
- 写信弹窗:写邮件/回复/回复全部/转发均打开可拖动、可缩放的写信窗口(默认 900×680), 编辑区随窗口高度伸展,页脚固定。
- 邮件列表:未读加粗、发件人、主题、日期、附件回形针;服务端搜索;刷新。
- 邮件预览:HTML 正文在沙箱 iframe 中渲染,内嵌(cid)图片自动内联显示, 远程图片默认屏蔽(可“显示图片”);附件列表支持逐个“保存到文档”; 回复 / 回复全部 / 转发 / 标记未读 / 加星标 / 删除(移到已删除)。
- 写信:新邮件、回复、回复全部、转发;多收件人(To/Cc/Bcc)与联系人自动补全; 群发支持合并发送与分别发送(群发单显)。
- 富文本编辑:撤销/重做、段落/标题、粗体、斜体、下划线、删除线、字号、字色、有序/无序列表、 引用、对齐、插入链接、清除格式;附件添加/移除(合计 ≤ 20 MiB)。
- 回复/转发自动带原文引用与线程头(In-Reply-To / References),发送成功后自动 APPEND 到已发送; Gmail 账号由 Gmail 自动保存已发送副本,插件不重复追加。
- 回复/转发保留原邮件正文内嵌图片(v1.15):引用原文里的
cid:图片随信以标准multipart/related(Content-ID)发送,收件人 Foxmail / Outlook / Gmail / 网页版均能正常显示; 写信区里直接可见引用的原图,粘贴的data:图片发信前也会自动转为内嵌部件。 - 列表姓名与会话聚合(v1.16):列表姓名始终加粗,已发送/草稿显示收件人;列表标题行可开关 「按主题聚合」(默认开启,参考 Foxmail 会话视图),同主题邮件合并为 N 封会话行、可展开逐封查看。
- 会话树与列表标签(v1.17):会话展开后以树状连接线展示组内邮件;列表行标签显示为「圆点 + 名称」
胶囊(每行 2 个 +
+N),会话组行显示组内标签并集。 - 回复写信区(v1.16):回复/回复全部/转发的正文顶部预留两行空白并自动聚焦(直接输入), 邮件签名前置到书写区之后、引用原文之上;新写邮件签名仍在正文末尾。
- 新邮件正文加速(v1.18):超过 256 KiB 的未缓存邮件改用
BODYSTRUCTURE+BODY.PEEK[分区]轻量拉取,只下载正文文本与预算内内嵌图片,不再把附件字节随正文下载(附件清单/索引与全量解析一致,新增bodyPartial标记);mail.prefetch后台预取同步覆盖大邮件,慢请求日志记录各阶段耗时与传输字节。 - 压缩包多格式(v1.19):
.zip用内置零依赖解析;.rar/.7z/.tar/.tgz等容器格式在系统存在解压后端时(macOS 内置bsdtar/libarchive 自动探测,否则 7z/7zz,可用环境变量覆盖)同样列出包内文件并逐文件预览;单流.gz/.bz2/.xz仅在所选后端支持原始流(如 7-Zip)时可预览,否则给出「暂不支持该压缩格式」提示;缺少工具、加密、损坏或超限时给出明确提示。
3. 邮件设置
- 每账号富文本签名(可启用/停用,写信时自动带入,可临时关闭)。
- 全局设置:文档保存目录(可校验可写性)、默认邮箱。
4. 附件预览(v1.1,v1.12 起 Office 版式还原)
- 双击附件即下载到本地缓存(
~/.dsh/dsh-mail/downloads/…)并弹出预览窗口,可拖拽缩放:- Office:零依赖、离线内置解析预览。
.docx渲染白纸页面(字号字色、粗斜体、对齐/缩进、 编号与项目符号、表格边框/底纹/合并、内嵌图片);.xlsx按 Excel 显示值渲染(日期/百分比/货币/ 千分位、单元格样式、合并单元格、列宽行高、行号 + 列字母表头、多工作表标签切换);.pptx按幻灯片真实画布比例与形状坐标还原(文字字号/颜色/对齐/项目符号、形状填充、背景、图片、表格)。 旧版.doc/.xls/.ppt等提供「在访达显示 / 用系统应用打开」。 - HTML:清洗后在沙箱 iframe 中预览,可一键用系统浏览器打开。
- PDF:内嵌预览 + 系统应用打开。
- 图片(png/jpg/gif/webp/bmp/svg):缩放/平移查看。
- 文本(txt/csv/json/xml/md…):等宽文本预览。
- 压缩包(v1.18 起,v1.19 扩展多格式):
.zip读取 ZIP 中央目录列出包内文件(名称 / 大小 / 类型,最多 500 个);.rar/.7z/.tar/.tgz/.tar.gz/.tar.bz2/.tar.xz等容器格式在系统存在解压后端(macOS 内置bsdtar/libarchive 自动探测,否则 7zz/7z;可用DSH_MAIL_ARCHIVE_TOOL/DSH_MAIL_ARCHIVE_BIN覆盖)时同样列出;单流.gz/.bz2/.xz仅在后端支持原始流(如 7-Zip)时可预览,否则提示暂不支持。点击某个文件即可在同一个预览窗内单独解析并预览(文本 / 图片 / Office / PDF / HTML…);摘要显示压缩包格式;加密文件、嵌套压缩包、缺少解压工具给出明确提示;单文件 ≤ 20 MiB。 - 预览窗页脚统一提供:在访达显示、用系统应用打开、保存到文档库。
- Office:零依赖、离线内置解析预览。
5. 文档管理
- 在邮件预览中把任意附件保存到本地文档:保存对话框可自定义文件名、子目录、标签、备注, 默认值来自附件名与配置的文档根目录。
- 文档库:按文件名/主题/发件人搜索,打开文件、在访达/文件管理器中显示、删除(含本地文件)。
- 文档根目录可配置,保存路径做了越界与重名防护。
6. 联系人
- 面向企业邮箱的联系人簿:手工新增/编辑/删除,按来源(手动 / 邮件收集 / 导入)与账号筛选。
- 企业通讯录导入:CSV / JSON,自动识别中英文列名 (姓名/name、邮箱/email、公司/org、部门/department、职位/title、电话/phone), 兼容企业微信、钉钉、Exchange 导出的联系人表。
- 从邮件收集:按账号抓取最近 N 封邮件头,自动汇总往来联系人(收件/发件计数、最近时间)。
- 写信收件人自动补全(服务端查询 + 本地缓存)。
7. 浅色 / 深色主题
- 阅读窗格、阅读/写信弹窗、附件预览窗、图片放大灯箱、邮箱设置与联系人/文档页均只使用 DSH 主题 token,
在浅色与深色模式下保持一致的层级、边界与可读性;原生表单控件跟随主题(
color-scheme)。
8. 全邮箱自定义标签(v1.3 / v1.4)
- 所有邮箱都能打标签:阅读窗格或阅读弹窗中打开「编辑标签」,勾选已有标签、只同步差异;
列表与阅读区显示彩色标签圆点。
- Gmail / Google Workspace:使用 Gmail 服务器标签(IMAP
X-GM-LABELS),与 Gmail 网页版双向同步; 新建标签即在 Gmail 中创建;Gmail 标签本身是 IMAP 文件夹,侧栏会以文件夹形式列出,点击即按标签筛选。 - 其它所有邮箱(QQ / 163 / 腾讯企业邮 / 阿里企业邮 / 自建 / Exchange IMAP 等,
:优先使用服务器 IMAP 关键字(keywords),与 Foxmail 等客户端
双向同步——Foxmail 中已有的关键字标签会自动出现在侧栏「标签」里,插件里打的标签也会写回服务器;
标签视图通过服务器
SEARCH KEYWORD查询该标签下的全部邮件(不是只查本地缓存)。 注意:腾讯企业邮的 Foxmail 标签可能由 Foxmail 私有协议同步,标准 IMAP 不暴露; 此时插件自动降级为本地标签,可用「设置 → 邮箱诊断」确认服务器是否暴露关键字。
- Gmail / Google Workspace:使用 Gmail 服务器标签(IMAP
- 内联新建标签:在标签弹窗输入名称即可创建(Gmail 层级标签用
项目/周报写法)。 - 标签弹窗会标出标签来源(Gmail 标签 / 服务器标签 / 本地标签);服务器关键字标签与 Gmail 标签的 改名/删除请在原客户端(Foxmail / Gmail 网页版)操作,插件内可直接打标签与新建。 收件箱/已发送等系统标签不可编辑;标签颜色由插件按名称哈希生成稳定颜色。
9. 快速加载与本地缓存(v1.4)
- 秒开列表:打开面板时先显示本机磁盘缓存中的文件夹/邮件列表(及最近读过的正文), 随后在后台自动刷新,不再每次await全局加载;标题旁会显示「正在后台更新…」与旋转刷新图标。
- 缓存位置:
$DSH_HOME/dsh-mail/cache/(默认上限约 256 MB,自动淘汰最旧记录); 设置页「本地缓存」可查看占用/文件数、刷新统计、一键清除(不影响服务器邮件与本地标签)。 - 附件始终按需:缓存只含邮件头、正文与附件元数据;附件在双击预览/保存时才下载, 不会被预取或塞进缓存。
- 首次加载无缓存时显示骨架屏;键盘
↑/↓切换当前邮件、Enter在阅读弹窗打开。
10. 智能文件夹视图(v1.5)
- 每个邮箱账号的侧栏在真实 IMAP 文件夹之外补充:
- 星标邮件:跨文件夹汇总已加星标的邮件(阅读窗格「加星标」即可标记)。
- 通知邮件:优先显示服务器上的「通知邮件」文件夹;没有该文件夹时,从收件箱智能筛选 系统通知类邮件(no-reply / 通知 / 提醒 / 验证码 / 账单 / 订阅等)。
- 已删除 / 垃圾箱 / 存档 / 自定义文件夹:按 IMAP 特殊用途自动识别并配图标;例如 已删除=Trash、垃圾箱=Junk、存档/Archive=Archive,招聘邮件等自定义文件夹照常显示。
- 标签分组(可折叠):显示本地标签(非 Gmail 邮箱),点击即进入该标签的邮件视图; 给邮件打/取消标签后即可在对应视图中看到。
- 智能视图支持分页、刷新与后台缓存刷新;邮件在阅读/回复/删除时自动使用其真实所在文件夹。
- 相关:Gmail 账号的标签仍以服务器标签文件夹形式出现在文件夹列表中(带彩色圆点)。
11. 服务器标签同步与已发送历史(v1.6)
- 标签写入服务器:非 Gmail 邮箱的标签通过 IMAP
STORE ±FLAGS写为服务器关键字(中文/空格名以 modified UTF-7 编码),Foxmail / 其它客户端可见;Foxmail 中已有的服务器标签会自动发现并显示在 「标签」分组,点开即用服务器SEARCH KEYWORD查询全部已标注邮件(跨文件夹聚合)。 Gmail 仍使用服务器X-GM-LABELS。 - 已发送历史:「已发送」文件夹默认加载全部历史(不再受“最近 50/100 封”窗口限制),支持翻页; 其它文件夹底部在窗口受限时显示「加载更早历史(共 N 封)」,一键切换到全量历史。
- Foxmail 风格 UI:侧栏分为「常用文件夹 / 我的文件夹 / 标签」分组,分隔线与选中态更清晰; 阅读区空状态改为大信封卡片;标签来源徽标区分 Gmail 标签 / 服务器标签 / 本地标签。
12. 服务器标签诊断(v1.7/1.8)
- 若「标签」分组为空(常见于腾讯企业邮 + Foxmail:标签可能由 Foxmail 私有协议同步,标准 IMAP 不暴露),
可在 设置 → 邮箱诊断 → 诊断当前邮箱 查看该账号的:
IMAP 能力、
PERMANENTFLAGS、文件夹列表、收件箱最近邮件的 flags/keywords、标签发现扫描统计与诊断提示。 - 诊断结论:
- 支持自定义关键字且找到关键字 → 标签读写正常,可继续使用服务器标签;
- 不支持自定义关键字/无关键字 → Foxmail 服务器标签无法通过标准 IMAP 读取; 此时侧栏「标签」分组仍可用:可直接新建插件本地标签并给邮件打标签(仅本机保存,不跨客户端同步)。
- v1.8 增强诊断:只读 SELECT 没有 PERMANENTFLAGS 时会自动用读写 SELECT 复检(显示来源);
新增「测试关键字写入」按钮(显式点击才执行):在收件箱最后一封邮件上临时加/删
$dshprobe关键字并自动恢复 flags(不改已读/星标),直接告诉你服务器是否接受自定义关键字。 - 侧栏「标签」分组现在始终可见:加载中/读取失败/空目录都有状态提示与「刷新标签」按钮, 非 Gmail 账号还可直接在分组底部输入名称新建标签。
13. 本地标签批量导入(v1.8)
- 当服务器确实不通过 IMAP 暴露标签(腾讯企业邮 + Foxmail 私有同步的常见情况)时: 设置 → 标签管理 → 批量导入标签名称,把 Foxmail 里的标签名(如 应龙版本迭代/项目周报/反馈&需求/ 数据调整/大湾区AI创新/ZHGC)按每行一个粘贴导入(也支持逗号/顿号分隔),即可在侧栏「标签」中 给邮件打标签并筛选;标签仅保存在本机,不会写回 Foxmail 服务器。
- 侧栏「标签」分组在诊断确认服务器不支持关键字后,会显式提示“此处为插件本地标签”。
- 当前标签以 chip 列表展示,可直接删除(删除只影响插件本地标签与关联)。
14. 新邮件检查与通知(v1.9)
- 后台检查(仅页面打开期间):DSH Web 页面打开时,客户端按设定间隔在后台调用只读的
mail.check接口检查每个已配置账号的收件箱(不检查其它文件夹、不写 flags、不改已读状态,多账号逐个检查); 有新邮件到达时更新侧栏红点并按需弹出应用内通知。关闭页面/退出应用后检查停止(不做 Host 常驻轮询)。 - 设置 → 新邮件通知:后台检查开关、检查间隔(30 秒 / 1 分钟 / 2 分钟 / 5 分钟 / 10 分钟)、 新邮件弹出通知开关、侧栏未读红点开关,以及「立即检查」按钮和上次检查时间/最近错误; 改动即时保存并立即按新间隔生效。关闭后台检查后不再定时检查,但「立即检查」仍可手动触发一次。
- 侧栏红点:侧栏「邮件」图标右上角显示红点,数字为各账号收件箱未读数之和(超过 99 显示
99+), 悬停提示「未读 N 封」;关闭「侧栏未读红点」开关或停用后台检查后不显示。红点数字来自服务器真实未读数。 - 应用内弹出通知:新邮件到达时在页面右上角堆叠显示通知卡片(账号、发件人、主题、时间), 最多同屏显示 3 张、其余汇总为「还有 N 封新邮件」;点击卡片打开邮件面板并定位到该邮件, 点「忽略」可单条关闭,也可一次性全部忽略。每张卡片 15 秒后自动消失,侧栏红点/未读数持续保留 (并在后续检查时按服务器真实未读数更新)。
- 基线语义:打开页面后的首次检查只建立基线——已有的历史未读邮件会照常计入红点/未读数, 但不会逐封弹出通知;只有页面打开期间新到达的邮件才触发弹出通知(同一会话内不会重复弹出)。
- 多账号与失败隔离:多个账号逐个检查、互不阻塞;单个账号认证/连接失败只记录错误(在设置页显示), 不影响其它账号检查,失败账号的红点计数保留上一次结果。
- 边界:只在 DSH Web 页面打开期间检查;不做系统级通知(不弹 macOS 通知中心,仅在页面内提示)。
15. 邮件详情 UI 优化(v1.10)
- 范围:仅 Client 详情 UI(右侧阅读预览窗格
MailPreviewPane与独立阅读弹窗MailReaderWindow), 不改 Host、RPC、数据模型与状态机字段,也不新增依赖;版本号 1.10.0(设置 → 关于显示 1.10.0)。 - 收件人折叠(To / Cc 各自独立):收件人超过 3 位时默认只显示前 3 位,行内显示「展开其余 N 位」
按钮;展开后显示全部收件人并变为「收起」,按钮带
aria-expanded状态。展开/收起只切换本地显示状态, 不触发任何服务器请求;展开后的收件人区域在 84px 高度内滚动,长列表不会把工具栏与正文挤出可视区。 每位收件人悬停显示完整地址,文本可安全换行(overflow-wrap:anywhere),不产生横向溢出。 - 附件折叠:附件超过 3 个时默认显示前 3 个,附件标题行右侧显示「展开全部(还有 N 个)」;
展开后显示全部附件、按钮变为「收起附件」,同样带
aria-expanded,且只改组件本地状态 (不触发 RPC、不清空阅读状态、不改当前选中邮件);展开后的附件列表在min(260px, 30vh)高度内 滚动,不会把操作栏与正文顶出可视区。 - 统一元信息网格:详情 From / To / Cc / Date 使用统一的两列 grid(
max-content minmax(0,1fr)), 标签列用次要文本色、值列用主文本色,窄窗口自动换行、不换列不重叠。 - 配色精简(详情区):移除详情区的大块浅灰底色——附件行改为透明底 + 0.5px 细分割线,
阅读区元信息、独立阅读弹窗头部与「图片已隐藏」提示条改为透明/基色底;hover 仅使用主题 token
(
interactive-bg-hover/bg-layer-2),不写死字面量颜色、无渐变,浅色/深色主题一致。 - 窄窗口:附件行的预览/保存按钮固定在右侧不换行,窗口较窄时保存按钮只显示图标(提示文案不变), 避免按钮挤压文件名、避免横向溢出。
- 验收:
node --test tests/*.test.mjs(含 v1.10 折叠渲染与 CSS 断言)全绿;node --check client.js; 刷新/重启应用后,用「多收件人 + 多附件」邮件验证默认折叠 3 条、可展开/收起,详情区无大面积浅灰底色。
16. 邮件正文加载优化(v1.11)
- 范围:Host(
lib/service.js、index.js)+ Client(client.js),版本号 1.11.0(设置 → 关于显示 1.11.0); 不改数据模型、不加依赖。目标:正文读过一次就接近瞬时打开;未读首次打开也有「表头先出现、正文随后」的 体感;后台预取把最可能点击的正文提前落到本机缓存。 - 正文磁盘缓存不再过期(跨重启生效):
mail.get不再受旧的 5 分钟 TTL 限制——只要本机磁盘缓存中 已有该账号/目录/UID 的正文($DSH_HOME/dsh-mail/cache/,仅本机 0600 文件),就直接返回缓存 (cached:true+fetchedAt)且不连接 IMAP;已读/读过的邮件再次打开(包括重启 DSH Web 后)接近瞬时。 正文被视为不可变,已读/星标/标签等状态仍由其它 RPC 维护;需要强制重取时用刷新(refresh:true), 缓存未命中时仍走一次 IMAP 拉取并回写缓存。 - 缓存命中的未读邮件:立即显示 + 后台补写「已读」:缓存正文未读、本次打开需要标记已读时,Host
立即返回缓存正文并在响应中带
pendingMarkSeen:true,随后才在后台用一次 IMAP 连接 best-effort 补写+FLAGS (\Seen),并同步正文缓存与邮件头缓存;补写失败只记 Host 日志,不影响本次打开,也不回滚 客户端已乐观更新的已读状态(后续列表/检查会与服务器对齐)。已是已读的缓存邮件直接返回,不发 IMAP 请求。 - 新增只读 RPC
mail.prefetch(METHODS 38→39):客户端在后台把「当前列表页 + 相邻邮件」的正文提前 写入本机磁盘缓存,让随后点击几乎无需等待。请求{accountId, folder?, uids}:uids为 1..10 个正整数 (重复自动去重),folder缺省INBOX;只读、永不标记已读(内部恒markSeen:false,不写 flags、 不触发pendingMarkSeen后台标记)。体积上限:头缓存已知单封 > 300 KiB 的邮件直接跳过; 单次请求累计抓取上限 2 MiB,超出后的剩余 uid 跳过;磁盘缓存已命中直接计入(不重复抓取)。 单封失败(IMAP 错误/解析失败)只在对应条目返回ok:false,不影响同批其它邮件;只有参数非法才整体bad_request。响应{warmed, skipped, failed, results:[…]}。 - 客户端:表头秒出 + 会话 LRU + 后台预取:
- 表头即时渲染:打开未命中缓存的邮件时,先用
mail.list的列表行立即渲染完整表头 (主题 / From / To / Cc / 日期与操作按钮),正文区域显示轻量骨架「正在加载正文…」,等正文返回后 再填充;没有对应列表行时才回退整块加载状态,从而消除空白等待感。 - 会话正文 LRU(30 条):当前会话内保留最近 30 封正文的内存缓存(上限
DETAIL_CACHE_LIMIT=30), 来回切换邮件直接命中、不再发mail.get;删除/已读/标签修改会同步更新对应缓存条目。 - 后台预取:列表加载完成或打开邮件后,客户端在空闲时(去抖 600ms)静默调用
mail.prefetch: 当前页最多 5 封(PREFETCH_PAGE_LIMIT=5),打开邮件的前后各 1 封(PREFETCH_NEIGHBOR_LIMIT=1); 只挑体积已知且 ≤ 300 KiB(PREFETCH_MAX_SIZE)的邮件,每批 ≤ 10 个 uid(PREFETCH_UID_LIMIT=10), 同一会话内去重;仅在普通文件夹视图触发(星标/标签等虚拟视图不预取),账号/文件夹切换后迟到的结果 直接丢弃。预取不改列表、不弹 toast、不改变 loading/错误状态,失败静默。
- 表头即时渲染:打开未命中缓存的邮件时,先用
- 诚实边界:首次打开从未缓存过的邮件仍需一次 IMAP 拉取(耗时取决于服务器/网络,Host 侧对单封 正文仍保留既有大小上限保护);预取只是后台尽力而为、受上述体积与数量上限约束;页面关闭后预取停止; 后台补写已读失败时以服务器后续状态为准。
- 验收:
node --test tests/*.test.mjs全绿;node --check client.js;刷新/重启后:已读邮件二次打开秒开, 未读邮件打开时表头立即出现、正文尽快显示,连续切换邮件明显更快。
17. Office 附件预览优化(v1.12)
- 范围:Host(
lib/office.js拆出共享层lib/office-xml.js,新增lib/office-docx.js/lib/office-xlsx.js/lib/office-pptx.js三个纯函数提取模块,lib/preview.js透传富载荷)+ Client(client.js新增DocumentPreview/SpreadsheetPreview/SlidesPreview渲染组件);版本号 1.12.0(设置 → 关于显示 1.12.0)。 零第三方依赖、离线可用、无新 RPC(仍 39 个)、无数据模型变更;v1.1 的html/sheets[].rows/slides[].paragraphs字段语义保持不变,作为降级路径。 .docx富文档模型:解析document.xml+styles.xml+numbering.xml+ rels +media/*,输出preview.doc(页面尺寸/页边距、docDefaults 字体、段落块/表格/图片的运行级格式)。渲染为白纸页面: 标题/正文样式继承、粗体/斜体/下划线/删除线、字色/字号/字体/高亮/上下标、对齐/缩进/段距/行距、 编号与项目符号(多级 marker)、表格 gridSpan/vMerge/边框/底纹/列宽、内嵌图片(EMU 尺寸、rels→data URL)、 超链接以蓝色下划线文本展示(不生成可导航链接)。图片上限 40 张 / 单张 ≤ 2 MiB / 合计 ≤ 6 MiB。.xlsx富网格模型:解析workbook.xml+styles.xml+sharedStrings.xml+ 工作表/rels/theme,输出sheets[].grid(已格式化的显示值行、列宽、行高、合并区域、稀疏单元格样式表)。支持日期(含 1904 工作簿)、时间、百分比、货币/会计、千分位、小数位、科学计数、分数、文本等常见 numFmt;字体(粗斜体/ 字号/颜色/字体名)、填充色、水平/垂直对齐、自动换行。渲染为带行号 + 列字母的网格:多工作表标签切换、 合并单元格、列宽行高、数字默认右对齐、超长文本省略并悬停显示全文;首屏 200 行 + 「显示更多行」。.pptx富版式模型:解析presentation.xml幻灯片尺寸、逐页slideN.xml形状树(p:sp/p:pic/p:grpSp/p:graphicFrame表格)、layout/master 占位符位置继承、theme 颜色与 master 文字样式继承、 背景填充,按真实画布比例与 EMU→px 坐标绝对定位渲染;文字字号/颜色/粗斜体/对齐/项目符号、形状填充、 图片、表格均可还原。图片合计上限 6 MiB,单页形状上限 300。- 安全与降级:富模型只含纯文本/
#rrggbb/data:image/*,客户端逐节点构建 React 元素(不使用dangerouslySetInnerHTML、不导航外链);富数据缺失、损坏或超过 8 MiB JSON 上限时自动回退到 v1.1 旧渲染并提示office-rich-omitted;Office 解析失败仍降级unsupported(可用系统应用打开)。 - 验收:
node --test tests/*.test.mjs全绿(新增 docx/xlsx/pptx/client-office 测试与v112-verify);node --check lib/*.js client.js;tools/office-preview-lab/提供样例 + LibreOffice 参考图 + 插件预览无头截图的前后对比(node tools/office-preview-lab/run.mjs --all)。
18. 标签同步修正与腾讯企业邮服务器标签(v1.13)
- 版本:1.13.0(设置 → 关于显示 1.13.0);无新 RPC(仍 39 个)、零第三方依赖、数据模型只增。
- Bug 修复:非 Gmail 账号不再出现 Gmail 文案。此前「编辑标签」弹窗副标题与提示是写死的 Gmail 文案
(腾讯企业邮账号也会显示「为这封信勾选 Gmail 标签」),且服务器标签订阅未就绪时还会回落到 Gmail 说明。
v1.13 起标签弹窗按账号服务商与标签来源(
gmail/xtag/keyword/local)取文案:Gmail 保留 Gmail 措辞,腾讯企业邮/QQ/163/自建 IMAP 使用「服务器标签」措辞,本地降级使用「本地标签」措辞。 - 腾讯企业邮服务器标签(v1.13 新能力):腾讯企业邮的「标签」保存在服务器端,但不走标准 IMAP
keyword,而是 Foxmail 的私有 IMAP 扩展
XTAG。插件现在在连接imap.exmail.qq.com时发送ID (... "exmailtag" "1")解锁该扩展:XTAGLIST读取服务器标签目录(名称 + 颜色),侧栏「标签」分组直接显示,与 Foxmail / 网页版一致;- 邮件 FLAGS 里的
T_<tagId>即邮件所属标签,列表/详情显示彩色标签圆点; - 在插件里给邮件打/取消标签会写回服务器(
UID STORE ±FLAGS (T_<tagId>)),Foxmail 与网页版可见; - 在标签弹窗新建标签会通过
XTAGCREATE在服务器创建(颜色按名称生成),立刻同步到 Foxmail/网页版; - 标签视图用服务器 FLAGS 筛选(与其它邮箱的关键字标签机制一致)。
- 改名/删除标签仍在 Foxmail 或网页版操作(插件内不提供,避免私有协议误写);删除标签会级联清除 邮件上的标签,插件仅展示、不执行破坏性操作。
- 降级语义修正:普通 IMAP 服务器若拒绝自定义关键字、或「回 OK 但静默丢弃」(腾讯企业邮的普通
keyword 正是如此),
mail.label不再谎报「服务器标签」,而是返回source:'local'并带warning:'keyword-store-failed' | 'keyword-store-not-persisted',客户端显示「本地标签」徽标并弹出 说明提示;label.list新增provider字段(gmail/xtag/keyword/local)供客户端选择文案。 - 诊断增强:邮箱诊断对腾讯企业邮账号额外显示
xtag(是否支持 + 服务器标签目录); 关键字扫描范围由 ≤12 个文件夹提升到 ≤40 个可选文件夹(每夹最近 200 封,120 s 缓存)。 - 兼容性:仍零第三方依赖、无新 RPC(39 个)、数据模型只增(本地标签字段不变、无需迁移); 非腾讯企业邮账号行为不变;腾讯企业邮在 XTAG 不可用时自动回退到原关键字/本地标签路径。
- 验收:
node --test tests/*.test.mjs全绿(新增 imap/service/client 的 XTAG 用例与docs/exmail-label-research.md协议调研);node --check client.js lib/*.js index.js; 重启 DSH 后:腾讯企业邮账号侧栏出现服务器标签(如「应龙版本迭代」),打标签后 Foxmail/网页版可见, 新建标签出现在服务器与 Foxmail;非 Gmail 账号标签弹窗不再出现任何 Gmail 字样。
19. 标签过滤修复、正文图片修复与列表/正文体验优化(v1.14)
- 版本:1.14.0(设置 → 关于显示 1.14.0);METHODS 39→40(仅新增只读的
attachment.inline, 用于按需取正文内嵌图片)、零第三方依赖、数据模型不变。 - Bug 修复:点击腾讯企业邮标签不再过滤列表。根因:腾讯企业邮的 IMAP 服务器把不支持的
UID SEARCH KEYWORD T_*当成「全部邮件」,导致标签视图返回整个邮箱。v1.14 起腾讯企业邮标签视图 改为「扫描各文件夹邮件 flags → 只保留含该标签T_<tagId>的消息」,结果正确且带 60 秒内存缓存 (列表刷新按钮可强制重查);其它邮箱仍用标准关键字搜索,Gmail 仍不支持标签视图。 - Bug 修复:部分邮件正文图片加载失败。两个真实原因及修复:
- 内嵌图片的
Content-Type被发送方错标为application/octet-stream→ Host 按文件魔数 (PNG/JPEG/GIF/WebP/BMP)识别为图片并生成正确image/*内嵌数据; - 内嵌图片超过单图 512 KiB / 总量 3 MiB 预算时会缺图 → 超预算图片不再塞进正文载荷,而是标记
inlinePending,客户端打开正文后按需批量调用attachment.inline拉取并回填(单图 ≤8 MiB、 单次 ≤16 MiB、≤200 张);正文首屏不等图片、先显示占位,图片到达后自动替换,不出现破图图标。 这样既修复了破图,也显著减小正文载荷、加快正文打开与磁盘缓存命中。
- 内嵌图片的
- 新功能:邮件列表行二级操作(参考 Foxmail)。在中间列表的邮件行上右键弹出上下文菜单: 回复 / 回复全部 / 转发 / 在阅读窗格打开 / 在新窗口打开 / 添加标签 / 标记已读或未读 / 星标或取消星标 / 删除(沿用确认弹窗);支持键盘上下选择、Enter 执行、Esc 或点空白关闭,菜单自动限制在视口内; 双击打开、单击选中、滚动、未读加粗等原有行为不变。
- 优化:详情区更省空间。收件人(To/Cc)与附件列表默认只显示一行(1 条),其余折叠为 「展开(还有 N 条)」;展开后仍在各自区域内滚动,阅读窗格与浮动阅读窗一致。
- 优化:列表 / 标签 / 正文加载速度:
- 本地邮件缓存升级
CACHE_VERSION=2(v1.13 及更早的旧缓存记录一次性重建,之后缓存命中更快); 列表/正文缓存命中不再为了补标签而额外回连服务器;新缓存记录自带标签数组(空数组也保留)。 - 腾讯企业邮标签视图 60 秒内存缓存;写入/新建标签、刷新列表时自动失效;冷路径实测约 1–2 秒, 热路径 2–3 毫秒。
- 客户端标签智能视图列表 60 秒缓存(变更操作自动失效)、智能视图刷新不再重复拉文件夹树。
- 正文内嵌图片改按需加载(见上),大图邮件首屏正文明显变小。
- 本地邮件缓存升级
- 验收:
node --test tests/*.test.mjs全绿(新增 v114 验收与 imap/service/client 用例);node --check client.js lib/*.js index.js;重启 DSH 后:点「应龙版本迭代」等标签只显示对应邮件、 打开含大图/截图邮件正文图片正常显示、列表行右键菜单可用、收件人/附件默认一行折叠。
20. 回复/转发正文内嵌图片随信保留(v1.15)
- 版本:1.15.0(设置 → 关于显示 1.15.0);METHODS 保持 40(无新 RPC;
mail.send的message.inlineImages为新增可选参数)、零第三方依赖、config.json数据模型与缓存版本不变。 - Bug 修复:回复/转发时原邮件正文图片丢失。根因:引用原文 HTML 里的内嵌图片是
<img src="cid:...">(Content-ID 引用),而发信时只生成 text/html 正文、从不携带对应的内嵌 MIME 部件,收件人客户端无法解析cid:→ 图片全部缺失(用 2026-10-30 两封真实回复复现:已发送 副本的 HTML 分别含 2 / 4 个cid:引用、附件为空)。v1.15 起:- Client 在回复/转发时从原邮件
attachments[](inline:true且已内嵌 dataUrl)收集被引用 HTML 引用的内嵌图片,随写信草稿携带;超过内嵌预算的大图在打开写信后按需调用既有只读attachment.inline一次拉回;写信区里这些图片直接显示(不再出现破图图标),编辑时 HTML 仍保持cid:形式(不把 base64 塞进编辑器状态)。 mail.send新增可选message.inlineImages: [{contentId, filename, contentType, dataBase64}]; Host 生成标准multipart/related(Content-ID: <contentId>、Content-Disposition: inline), 有普通附件时再包一层multipart/mixed;收件人(Foxmail / Outlook / Gmail / 网页版等)正常显示。- 兜底:写信区里粘贴/签名带入的
data:image/*;base64图片在发送前也会自动转为内嵌 MIME 部件, 不再发送收件人无法显示的 data: URL。单图 ≤ 8 MiB、合计 ≤ 16 MiB、≤ 200 张,超限在发送前提示。 - 不改变阅读区渲染与远程 http(s) 图片的既有权限策略;打开写信时原图拉取失败则发送时保留缺失, 不阻塞发送。
- Client 在回复/转发时从原邮件
- 验收:
node --test tests/*.test.mjs全绿(新增 v115 验收与 mime / service / host / client 用例);node --check client.js lib/*.js index.js;重启 DSH 后:回复带截图的原邮件 → 写信区可见被引用图片, 收件人收到的回复正文图片正常显示,已发送副本打开图片正常。
21. 列表姓名加粗、按主题聚合与回复写信区优化(v1.16)
- 版本:1.16.0(设置 → 关于显示 1.16.0);METHODS 保持 40(无新 RPC、
index.js/lib/*不变, 纯 Client 迭代)、零第三方依赖、config.json数据模型与缓存版本不变。 - 列表姓名列:姓名始终加粗(未读圆点与未读主题半粗保留未读辨识度);已发送 / 草稿文件夹 的列表姓名改为显示收件人(无姓名用邮箱,多收件人完整列表见悬停提示),其余文件夹/智能视图 仍显示发件人。
- 按主题聚合(参考 Foxmail 会话视图):列表标题行新增「按主题聚合」开关(默认开启,可随时
关闭;记忆在浏览器
localStorage)。开启后同一文件夹内主题相同(自动忽略Re:/Fwd:/回复:/转发:等前缀)的邮件合并为一条会话行:显示最新一封的姓名/主题/时间、未读圆点、 星标与回形针、以及「N 封」计数;点击箭头或会话行展开后在组内缩进列出每一封,原有打开、双击 新窗口、右键菜单、选中逻辑不变;组内邮件被选中时自动保持展开。搜索与智能视图(星标/通知/标签) 自动退回平铺列表;「加载更多/全部历史」仍按邮件条数追加。 - 回复/转发写信区:点击回复/回复全部/转发后,正文顶部预留两行空白书写区并自动把光标放到 顶部(可直接输入);邮件签名插入在书写区之后、引用原文之上(Foxmail 习惯),签名开关与 发件账号切换时按当前模式重新插入且不会重复。只打开回复/转发而未编辑时关闭对话框不再提示 「丢弃草稿」。新邮件(新写)行为不变:签名仍在正文末尾。
- 验收:
node --test tests/*.test.mjs全绿(新增 v116 验收与 client 用例);node --check client.js lib/*.js index.js;刷新页面(或重启 DSH)后:列表姓名加粗、已发送显示 收件人、会话聚合可展开/关闭、回复时顶部有空白且签名在引用上方。
22. 会话树状列表与列表标签名称(v1.17)
- 版本:1.17.0(设置 → 关于显示 1.17.0);METHODS 保持 40(无新 RPC、
index.js/lib/*不变, 纯 Client UI 迭代)、零第三方依赖、config.json数据模型与缓存版本不变。 - 会话聚合树状 UI:按主题聚合的会话组行保持原有交互(展开/收起、未读、N 封计数、星标/附件), 视觉上增加展开态标记、固定箭头列与描边计数徽标;组内邮件改为树状连接线(左侧竖线 + 横向短线, 末项截断到行中心),子行左缩进 40px、字号略紧凑,父子层级一眼可辨;打开、双击新窗口、右键菜单、 未读/选中/标签等行为不变。
- 列表标签名称:列表行里的标签由「只有一个彩色圆点」改为紧凑胶囊:彩色圆点 + 完整标签名称,
每行最多显示 2 个、其余折叠为
+N(悬停可看全部名称);按主题聚合的会话组行同时显示组内 标签并集(最多 2 个 ++N)。详情区/阅读窗口的完整标签胶囊与标签数据来源不变。 - 验收:
node --test tests/*.test.mjs全绿(新增 v117 验收与 client 用例);node --check client.js lib/*.js index.js;刷新页面(或重启 DSH)后:会话展开可见树状层级、 列表行直接可见标签名称(如「数据调整」「ZHGC」)、组行显示组内标签。
23. 新邮件正文轻量拉取与压缩包附件预览(v1.18)
- 版本:1.18.0(设置 → 关于显示 1.18.0);METHODS 保持 40(压缩包内单文件预览复用
attachment.preview的可选entry参数,不新增 RPC)、零第三方依赖、config.json数据模型与 磁盘缓存版本(CACHE_VERSION=2)不变。 - 问题:从未打开过的新邮件正文加载慢。根因是
mail.get一直用一次BODY.PEEK[]拉取整封 RFC 822 报文再解析——邮件正文只要文本,但附件字节(本机缓存样本中 87 封正文有 12 封 >1 MiB、 最大约 32 MB,其中 32 封带附件)也被完整下载。正文延迟 ≈ IMAP 建连/登录(2–4 RTT)+ 整封报文 下载(几 MB 时常为 1–3 s)+ MIME 解码;已缓存邮件仍走 v1.11 的磁盘缓存,不受影响。 - 优化(
bodyPartial轻量正文):阈值LITE_BODY_SIZE_THRESHOLD = 256 KiB,以列表/检查缓存里 已知的RFC822.SIZE决定策略——不超过阈值或大小未知时仍走原来的单次全量拉取(行为/字节不变); 超过阈值时复用同一条连接:先UID FETCH … BODYSTRUCTURE + BODY.PEEK[HEADER],由新的lib/body-parts.js按与全量解析完全一致的深度优先顺序挑出正文文本分区与预算内内嵌图片分区, 再用一次BODY.PEEK[<section>]只取这些分区。附件仍以相同索引/名称/类型/大小出现在message.attachments,只是没有内容字节(原本也没有);内嵌图片沿用单图 512 KiB / 总量 3 MiB 预算, 超预算仍走attachment.inline按需加载,且该 RPC 同样只拉取所需图片分区、不再下载整封邮件。 详情新增 additive 字段bodyPartial: true;mail.prefetch对超大邮件不再跳过,而是走轻量路径并在 实际传输字节 上沿用 2 MiB 预算;解析后的 BODYSTRUCTURE 在内存里缓存 5 分钟(≤128 条)供attachment.inline复用。以下情况自动回退全量拉取(行为与 v1.17 相同):结构不可解析、嵌套message/rfc822、multipart/signed/multipart/encrypted、结构过深(>20)或部件过多(>200)、 选中分区声明总大小超LITE_MAX_PART_BYTES = 4 MiB、内嵌候选图片大小/类型无法判定。mail.get总耗时 ≥ 300 ms 时写一条 info 日志(模式 full/lite、连接/选择/结构/分区/解析各阶段耗时、传输字节, 不含正文/主题/密码),便于现场定位。 - 实测(离线、确定性夹具):正文 + 内嵌小图 + 2 MiB base64 xlsx 附件的邮件(整封 2,871,281 B),
全量拉取传输 2,871,281 B;轻量拉取只传输 684 B 的分区 literal(
HEADER334 B + 正文/内嵌图分区 350 B;BODYSTRUCTURE自身 568 B,协议载荷合计约 1.25 KiB),大附件分区(2 MiB xlsx)从不请求;attachment.inline夹具里只传输被请求的 614,400 B 图片分区。离线测试对全量/轻量详情逐字段比对 (主题/收发件人/text/html/附件数量/索引/文件名/类型/大小/contentId/inline 标记/小图 dataUrl,含 被错误标注类型的 content-id 内嵌图),并断言传输字节降幅。 - 压缩包附件预览:新增
lib/archive.js(仅node:zlib,零依赖)与 Host 预览类型archive:attachment.preview对.zip返回{kind:'archive', archiveFormat:'zip', entryCount, truncated, entries:[{path,name,size,compressedSize,encrypted,kind}], notes}(仍写出压缩包下载副本,页脚 「在访达显示/用系统应用打开/保存到文档库」照常可用)。列表只读 ZIP 中央目录、不解压任何条目; 点击文件时客户端用同一 RPC 加可选参数entry请求{preview}(第二次下载副本不写),Host 按需 解压单个条目后复用现有预览管线(文本/图片/HTML/PDF/docx/xlsx/pptx)。安全边界:列表最多显示 500 个 文件(中央目录扫描上限 5000 条,truncated提示)、单文件解压上限 20 MiB(inflateRawSyncmaxOutputLength防 zip bomb)、仅 stored/deflate、ZIP64 与多卷包拒绝、条目名只作展示绝不作为文件 路径、加密条目禁用并提示、嵌套压缩包不递归;非 ZIP 的.rar/.7z/.tar/.gz…仍为unsupported且带archive-unsupported-format提示。中文文件名按 UTF-8 标志(bit 11)→ UTF-8 → GBK 回退解码。 - 客户端:
attachment卡片双击.zip后,预览窗先显示文件名/大小/数量的摘要和「名称/大小/类型」 三列表格(加密行带锁标、禁用点击、显示截断/空包提示),点击文件切换为内层预览(窗口标题、副标题、 返回栏与面包屑同步更新);Esc 在内层先返回列表、再按一次关闭窗口;页脚动作始终作用于压缩包本身。mail.prefetch候选不再按 300 KiB 客户端过滤(大小由 Host 决定 full/lite),预取仍限 5 条/10 UID。 - 验收:
node --test tests/*.test.mjs全绿;node --check index.js client.js lib/*.js;node tools/office-preview-lab/run.mjs --all --theme both --no-generate --no-reference(Office 预览 无视觉回归;AFTER 证据已按 1.18.0 重生成)。人工:重启 DSH 应用后,① 打开一封带大附件、从未 读过的新邮件,正文应明显快于 v1.17(可在 Host 日志看到mail.get ... mode=lite);② 双击一个.zip附件,应看到包内文件列表,点.txt/.docx/图片可在同一窗口内预览、点「← 返回压缩包列表」 回到列表;③ 加密 zip / rar / 7z 附件有明确提示而不是报错。
v1.19 起,
.rar/.7z/.tar/.tgz等容器格式在系统存在解压后端(bsdtar/7z)时同样支持列表与逐文件预览;单流.gz/.bz2/.xz是否需要支持原始流的后端、以及各格式的完整限制见 §24;v1.18 的 ZIP 行为与上限不变。
24. 压缩包多格式预览(v1.19)
- 版本:1.19.0(设置 → 关于显示 1.19.0);METHODS 保持 40(复用 v1.18
attachment.preview的 可选entry参数,不新增 RPC)、零第三方依赖、config.json数据模型与磁盘缓存版本(CACHE_VERSION=2)不变。 - 背景:v1.18 的压缩包预览只内置了解析 ZIP,用户实际收到的
.rar附件仍显示「不支持的格式」。v1.19 在不引入 npm 依赖的前提下,复用系统自带的解压工具补齐 RAR/7z/TAR 等格式:macOS 自带/usr/bin/bsdtar(bsdtar 3.5.3 / libarchive 3.7.4,实测可读取 RAR5、7z、tar、gz 等),其它机器可安装bsdtar或 7-Zip CLI(7zz/7z/7za)。工具探测顺序:bsdtar(含tar且其版本输出为 bsdtar/libarchive) →7zz→7z→7za;也可用环境变量DSH_MAIL_ARCHIVE_TOOL=bsdtar|7z|auto、DSH_MAIL_ARCHIVE_BIN=/absolute/path覆盖。探测结果进程内缓存,不额外起进程。 - 支持的格式:
.zip(内置,stored/deflate,行为与 v1.18 完全一致);容器格式.rar、.7z、.tar、.tgz、.tar.gz、.tar.bz2、.tar.xz(系统解压后端;能否解压取决于本机 libarchive/7-Zip 对该格式的支持,失败会给出 明确提示而不是报错);单流压缩.gz/.bz2/.xz只有在所选后端支持原始单流时才能预览(本机实验:libarchive 的bsdtarCLI 不读裸单流,7-Zip 可以),不支持时显示「暂不支持该压缩格式」提示(而不是误报「解析失败」)。 - 实现:新增
lib/archive-external.js——createCaptureRunner用spawn(argv 数组、无 shell、无 stdin) 收集 stdout,带 30 s 超时(探针/通用 runner 默认 10 s)与输出上限(列表 16 MiB;单文件 20 MiB + 余量),永不 reject;createSystemArchiveTool负责后端探测、清单与单文件抽取。bsdtar 清单以LC_ALL=C运行并用 C 风格反转义还原中文/控制字符名; 7z 清单解析-slt字段(Path/Size/Folder/Encrypted)。单文件抽取走工具 stdout(bsdtar-xOf <file> -- <entry>、7zx -so -p- -- <file> <entry>),不把包内文件落盘;entry 预览需要临时文件时 只把压缩包本身写到downloads/.tmp/(0700/0600、随机名、finally删除,绝不使用包内文件名)。 清单上限沿用 v1.18:最多列出 500 个文件(扫描 5000 条,truncated),单文件预览 ≤ 20 MiB;加密条目、 超限、损坏、嵌套压缩包、工具缺失分别降级为unsupported+ 对应 note token(archive-entry-encrypted/archive-entry-too-large/archive-entry-unreadable/archive-nested/archive-tool-missing)。 - 载荷增量:
kind:'archive'载荷新增 additive 字段archiveFormat(zip|rar|7z|tar|tgz|gz|bz2|xz)、archiveEngine(native|system-tool)、archiveTool(bsdtar|7z),其余entries/entryCount/truncated/ download与entry参数语义与 v1.18 相同;ZIP 走内置路径、不会调用系统工具。 - 客户端:压缩包摘要显示「格式 RAR / ZIP …」;新增
preview.note.archive-tool-missing中英文提示;settings.version1.19.0。文件列表、加密禁用、内层预览、返回栏/Esc 语义与 v1.18 相同。 - 安全:系统工具只读取压缩包、结果经 stdout 进入内存(不按条目名落盘);不拼接 shell 命令;条目名仅作 展示与匹配;工具 stderr 只进日志、不回传;加密压缩包不允许交互式密码输入(stdio ignore),直接降级提示。
- 验收:
node --test tests/*.test.mjs全绿(新增tests/archive-external.test.mjs,其中真实 bsdtar 集成 用例在无工具的机器上自动 skip);node --check index.js client.js lib/*.js;node tools/office-preview-lab/run.mjs --all --theme both --no-generate --no-reference(Office 预览无回归)。 人工:重启 DSH 应用后双击.rar附件 → 应看到包内文件列表(格式显示 RAR),点.jpg/.docx/.svg在同一窗口内预览、可返回列表;若本机没有任何解压后端,会看到「缺少解压工具」提示而不是报错。 - 限制:外部格式依赖本机系统工具(macOS 自带 bsdtar 可用;Linux/Windows 需安装 bsdtar 或 7-Zip CLI),
工具缺失时
.rar/.7z/...仍不可预览(会明确提示);单流.gz/.bz2/.xz在 macOS 自带 bsdtar 下不可预览 (7-Zip 后端可以),会显示「暂不支持该压缩格式」;加密压缩包、ZIP64/多卷包、嵌套压缩包不递归; 清单/单文件上限同 v1.18;包内文件仍不支持单独保存到文档库(可先保存整个压缩包再用系统应用打开)。
安装
在 DSH Web 的插件管理器里安装本目录为本地 bundle:
- 打开「设置 → 插件 → 添加插件」,选择本目录(
dsh-mail/),或在 Agent 会话中由plugin_managerinstall_bundle指定绝对目录。 - 安装完成后左侧栏出现「邮件」图标;点击打开面板。
- 首次进入为“添加邮箱”空状态,按向导填入邮箱配置并测试连接。
安装/升级后如新代码未生效,重启一次 DSH 应用(插件包替换需要新的 JS 模块代)。 v1.9 新增 Host 路由
mail.check与 Client 侧栏红点/通知中心:升级 bundle 代码后请重启 DSH 应用, 以加载新的 Host 路由与 Client bundle(打包安装场景下 client-plugin 热重载可能不会自动应用)。 v1.10 仅为 Client 详情 UI 调整(无 Host/RPC/数据变更):升级后刷新一次页面通常即可生效; 若刷新后详情区仍是旧样式,再重启一次 DSH 应用。 v1.11 修改了 Host(mail.get缓存策略 + 后台补写已读 + 新 RPCmail.prefetch,METHODS 38→39) 与 Client(即时表头/会话 LRU/后台预取):升级 bundle 后请重启一次 DSH 应用,以加载新的 Host 路由 与 Client bundle;仅刷新页面可能仍在使用旧代码。 v1.12 修改了 Host(lib/office.js拆分 + 新增lib/office-docx.js/lib/office-xlsx.js/lib/office-pptx.js富提取模块 +lib/preview.js透传富载荷)与 Client(client.js富 Office 渲染组件):升级 bundle 后请 重启一次 DSH 应用,以加载新的 Host 模块与 Client bundle;仅刷新页面时 Office 预览仍会使用旧 Host 数据。 v1.13 修改了 Host(lib/imap.js增加腾讯企业邮 XTAG 私有扩展:ID/exmailtag、XTAGLIST、XTAGCREATE、T_<tagId>标志读写;lib/service.js增加 XTAG 标签目录/映射与降级语义;index.js透传provider/warning) 与 Client(provider-aware 标签文案、颜色圆点、降级提示):升级 bundle 后请重启一次 DSH 应用, 以加载新的 Host 模块与 Client bundle;仅刷新页面时标签仍在用旧逻辑。 v1.14 修改了 Host(腾讯企业邮标签视图改 flags 扫描 + 60s 缓存;内嵌图片魔数嗅探与大图inlinePending; 新 RPCattachment.inline,METHODS 40;CACHE_VERSION=2会重建一次旧缓存)与 Client(列表右键菜单、 收件人/附件默认一行折叠、正文图片懒加载、标签视图 60s 缓存):升级 bundle 后请重启一次 DSH 应用; 首次启动后列表/正文会因缓存版本升级重建一次(稍慢),之后恢复快速命中。 v1.15 修改了 Host(lib/mime.js新增 multipart/related 内嵌图片、lib/service.js校验并透传mail.send新增的可选message.inlineImages;METHODS 仍 40、无数据模型/缓存变更)与 Client (回复/转发引用图收集与显示、data:图片兜底、1.15.0 版本号):升级 bundle 后请重启一次 DSH 应用, 以加载新的 Host 模块与 Client bundle;仅刷新页面时回复内嵌图片仍会走旧逻辑。 v1.16 仅改 Client(client.js:列表姓名加粗/已发送显示收件人、按主题聚合、回复写信区空白与 签名前置、1.16.0 版本号;无 Host/RPC/数据模型变更):升级 bundle 后刷新一次页面通常即可生效; 若列表/写信区仍是旧样式,再重启一次 DSH 应用。 v1.17 仅改 Client(client.js:会话树状连接线 + 列表标签名称胶囊、1.17.0 版本号;无 Host/RPC/ 数据模型变更):升级 bundle 后刷新一次页面通常即可生效;若列表仍是旧样式,再重启一次 DSH 应用。 v1.18 修改了 Host(新增lib/body-parts.js;lib/imap.js/lib/mime.js/lib/service.js增加 BODYSTRUCTURE 分区轻量正文拉取、attachment.inline/mail.prefetch轻量路径与慢请求日志;新增lib/archive.js,lib/preview.js/index.js增加 ZIP 压缩包列表与attachment.preview的可选entry单文件预览,METHODS 仍 40)与 Client(client.js:压缩包文件列表/逐文件预览 UI、预取候选 不再按大小过滤、1.18.0 版本号):升级 bundle 后请重启一次 DSH 应用,以加载新的 Host 模块与 Client bundle;仅刷新页面时大邮件正文仍走旧的全量下载、.zip附件仍显示「不支持预览」。 v1.19 修改了 Host(新增lib/archive-external.js系统解压后端(bsdtar/7z 探测与清单/抽取)、lib/archive.js扩展多格式识别、lib/preview.js/index.js让.rar/.7z/.tar/...复用 archive 预览与entry参数,METHODS 仍 40)与 Client(client.js:压缩包格式标签、缺少解压工具提示、 1.19.0 版本号):升级 bundle 后请重启一次 DSH 应用,以加载新的 Host 模块与 Client bundle; 仅刷新页面时.rar等附件仍会显示「不支持预览」。
邮箱配置帮助
常见邮箱需要“客户端授权码/应用专用密码”,而不是登录密码:
| 邮箱 | IMAP | SMTP | 备注 |
|---|---|---|---|
| Gmail | imap.gmail.com:993 SSL | smtp.gmail.com:465 SSL | 需开启两步验证并使用 16 位应用专用密码;插件不支持 OAuth 登录 |
| QQ 邮箱 | imap.qq.com:993 SSL | smtp.qq.com:465 SSL | 需在网页版开启 IMAP/SMTP 并使用授权码 |
| 163 / 126 | imap.163.com:993 SSL | smtp.163.com:465 SSL | 需开启客户端授权码 |
| 腾讯企业邮 | imap.exmail.qq.com:993 SSL | smtp.exmail.qq.com:465 SSL | 使用邮箱密码或客户端专用密码 |
| 阿里企业邮 | imap.qiye.aliyun.com:993 SSL | smtp.qiye.aliyun.com:465 SSL | — |
| Exchange / Outlook | 由管理员提供 IMAP 主机 | 同左 | 本插件不实现 Exchange EWS/Graph/OAuth2,请使用 IMAP/SMTP 或应用密码 |
- 端口 143/587/25 时使用 STARTTLS;端口 993/465 使用 SSL。
- 自建服务器证书自签时,打开“允许自签证书”(仅信任该主机,明文凭据仍有风险,请自行评估)。
- 文件夹(已发送/垃圾箱/草稿)默认自动探测(按
\Sent等 IMAP 标志与中英文名称), 探测不准时在账号编辑里手动指定。
Gmail 配置步骤
- 在 Gmail 网页版打开 设置 → 转发和 POP/IMAP,把 IMAP 设为“启用”: https://mail.google.com/mail/u/0/#settings/fwdandpop。
- 在 Google 账号中开启两步验证(应用专用密码以它为前置条件)。
- 打开 https://myaccount.google.com/apppasswords 创建应用专用密码(16 位;Google 展示时带空格, 粘贴进插件前建议去掉空格),把它填入账号编辑的“密码”框(不是 Google 登录密码)。
- 在“添加邮箱账号”里选择服务商 Gmail(含 Google Workspace):收件/发件服务器、端口、SSL 与
[Gmail]/Sent Mail · [Gmail]/Trash · [Gmail]/Drafts文件夹映射会自动填好,点“测试连接”验证后保存。
- Gmail 会在 SMTP 发信成功后自动把副本存入
[Gmail]/Sent Mail,插件不会重复 APPEND,避免出现两封已发送。- Google Workspace 管理员可能禁用应用专用密码;若登录失败请联系管理员,或改用管理员签发的凭据。
- 插件走标准 IMAP/SMTP 基本认证,不支持 OAuth2(Google 已不再支持“低安全性应用”登录方式)。
- Gmail 标签通过 IMAP 扩展
X-GM-LABELS读写(新建标签即CREATE),因此插件里打的标签会与 Gmail 网页版双向同步; 若账号未启用 IMAP 则标签功能也不可用。
安全与数据
- 账号密码保存在本机
$DSH_HOME/dsh-mail/config.json,文件权限0600,目录0700, 仅本机用户可读;接口永不回传密码(只返回hasPassword)。 - 邮件正文会写入本机磁盘缓存(
$DSH_HOME/dsh-mail/cache/,目录 0700、文件 0600,仅本机用户可读, 可在设置页一键清除);缓存未命中的正文按需从服务器读取。 - 附件保存路径经规范化校验,禁止
..越界;同名自动追加(2)。 - 预览 HTML 经过清洗并在
sandbox=""iframe 中渲染,脚本被浏览器沙箱阻断,远程图片默认不加载。 - 建议使用邮箱“授权码/应用专用密码”,不要使用主密码;本插件不会把凭据发送给任何第三方。
目录结构
dsh-mail/
├── package.json # bundle 清单(dsh.bundle.patch + dsh.client)
├── cordis.patch.yml # 插入 id=mail 的插件行
├── icon.svg # 插件图标
├── locale/{zh,en}.json # 插件管理器显示标题/描述
├── index.js # Host 入口:POST /api/dsh-mail/rpc + RPC 分发
├── client.js # Client 半边:侧栏入口 + 主面板(全部 UI)
├── lib/
│ ├── imap.js # IMAP4rev1 客户端(TLS/STARTTLS、modified UTF-7、FETCH/STORE/APPEND、
│ │ # v1.13 腾讯企业邮私有 XTAG 标签扩展:exmailtag ID/XTAGLIST/XTAGCREATE)
│ ├── smtp.js # SMTP 客户端(TLS/STARTTLS、AUTH PLAIN/LOGIN、点填充)
│ ├── mime.js # RFC 5322/2047/2045 解析与构造
│ ├── store.js # config.json 持久化(原子写、0600/0700、损坏恢复)
│ ├── service.js # 邮件业务编排(加载窗口、分页、发送、附件提取、联系人、XTAG 标签、v1.14 标签视图 flags 扫描/内嵌图片嗅探)
│ ├── preview.js # 附件下载缓存 + 预览载荷分发 + open/reveal 路径校验
│ ├── cache.js # 本地邮件缓存(文件夹/列表/正文、容量上限与清理;v1.14 CACHE_VERSION=2)
│ ├── office.js # 零依赖 Office 预览入口:ZIP 读取、旧版字段 + v1.12 富模型合并
│ ├── office-xml.js # v1.12 共享层:ZIP 读取 + 线性 XML 扫描 + rels 关系 + OfficeError
│ ├── office-docx.js # v1.12 docx 富提取(页面/样式/编号/表格/图片 → doc blocks)
│ ├── office-xlsx.js # v1.12 xlsx 富提取(显示值格式化/样式/合并/列宽行高 → grid)
│ ├── office-pptx.js # v1.12 pptx 富提取(画布/形状/主题色/占位符/图片/表格 → layout)
│ ├── contacts.js # 联系人 CSV/JSON 导入与聚合
│ └── documents.js # 附件落盘与文档根目录管理
├── tests/ # 离线测试(mock IMAP/SMTP、无外网;含 Office 富提取、XTAG 标签、v1.14 标签过滤/图片懒加载与 v112/v114 验收)
├── tools/
│ └── office-preview-lab/ # v1.12 视觉验证实验室:合成样例 + LibreOffice 参考图 +
│ # 无头 Chrome 截图 + before/after 对比(开发/验收工具,不进 bundle)
├── docs/VERIFICATION.md # 独立验证报告(含 v1.12 附件预览、v1.13 标签同步、v1.14 标签过滤/图片/性能认证)
├── docs/exmail-label-research.md # v1.13 腾讯企业邮标签协议调研(XTAG/EAS/Web API 实测证据与风险)
├── CONTRACT.md # Host/Client 接口与数据契约
└── README.md
测试
全部测试离线运行(仅连接 127.0.0.1 上的 mock 服务器):
cd dsh-mail
node --test tests/mime.test.mjs tests/smtp.test.mjs tests/imap.test.mjs
node --test tests/store.test.mjs tests/host.test.mjs tests/cache.test.mjs
node --test tests/service.test.mjs
node --test tests/office.test.mjs tests/preview.test.mjs
node --test tests/client.test.mjs
node --test tests/acceptance.test.mjs
# 或一次跑完(全部离线):
node --test tests/*.test.mjs
已知限制
- 正文轻量拉取(v1.18):只对「已知
RFC822.SIZE> 256 KiB 的未缓存邮件」生效;大小未知(无列表/检查 缓存摘要)时仍走一次全量拉取。结构不可解析、嵌套message/rfc822、multipart/signed/encrypted、 结构过深/部件过多、正文+内嵌分区声明总大小 >4 MiB、内嵌候选图片大小/类型无法判定时自动回退全量 下载(等价 v1.17)。轻量详情里的非内嵌附件只有元数据(名称/类型/大小/索引),双击预览/保存到文档库/ 压缩包内查看仍按既有 RPC 重新获取整封邮件(需要附件字节);内嵌图片超过单图 512 KiB / 总量 3 MiB 预算时仍由attachment.inline按需加载。base64 附件的声明大小可能比实际解码字节略大(≤2 B), 少数极端邮件会因此触发一次全量回退。 - 压缩包预览(v1.18,v1.19 扩展):
.zip由内置解析(stored/deflate;中文名按 UTF-8(bit 11)→ UTF-8 → GBK 回退);.rar/.7z/.tar/.tgz/.gz/.bz2/.xz等外部格式依赖本机系统解压工具——macOS 自带bsdtar(libarchive)可用,Linux/Windows 需自行安装bsdtar或 7-Zip CLI(7zz/7z/7za),也可用DSH_MAIL_ARCHIVE_TOOL/DSH_MAIL_ARCHIVE_BIN指定;缺失工具时这些格式会明确提示「缺少解压工具」 而不可预览。加密压缩包、ZIP64、多卷包、嵌套压缩包不递归预览;列表最多显示 500 个文件(扫描上限 5000,超出提示「列表已截断」);单文件预览 ≤ 20 MiB、清单/抽取各 30 秒超时;单流.gz/.bz2/.xz需要后端支持 原始单流(macOS 自带 bsdtar 不支持、7-Zip 支持),否则提示「暂不支持该压缩格式」;包内文件名仅作展示, 预览不写第二个下载副本,也不支持单独保存到文档库(可先保存整个压缩包再用系统应用打开)。 - 本地缓存只覆盖已打开过的文件夹/邮件正文(含当前页与相邻邮件的后台预取;磁盘,可清除), 不做本地全文索引;完全断网时未缓存的邮件不可用(已缓存的列表与正文仍可查看;账号、联系人、 文档、本地标签可管理)。
- 不做 OAuth2 / Exchange EWS / POP3 / LDAP 企业目录实时查询;Gmail / Google Workspace 需使用应用专用密码(若管理员禁用则无法用本插件登录)。企业通讯录通过 CSV/JSON 导入 + 邮件往来自动收集。不做常驻/离屏后台收信:v1.9 的新邮件检查只在 DSH Web 页面打开期间按设定间隔运行(关闭页面/退出应用后停止),也不注册系统级通知(仅应用内弹出 + 侧栏红点)。
- 不把草稿同步到服务器(关闭写信对话框会确认丢弃)。
- Office 内置预览为版式级近似还原(docx 页面/段落/表格/图片、xlsx 显示值与网格样式、pptx 画布/形状/文字/图片/表格), 仍不渲染图表、SmartArt、动画、批注、页眉页脚与公式编辑态;需要 100% 原始版式时用「用系统应用打开」。 图片/HTML/PDF/文本为原样预览。
- “从邮件收集”按所选文件夹最近 N 封汇总,不是全量索引。
- 标签:Gmail / Google Workspace 使用服务器
X-GM-LABELS;腾讯企业邮通过其私有 IMAPXTAG扩展 双向同步服务器标签(v1.13 起,含读取/打标签/新建;与 Foxmail、网页版一致;依赖腾讯服务器未公开的XTAG命令与exmailtagID 开关,若服务器升级改变协议会自动回退到本地标签);其它邮箱使用服务器 IMAP 关键字,服务器拒绝或静默丢弃关键字时准确降级为本地关联并在界面提示(不再冒充服务器标签)。 Gmail/腾讯企业邮/服务器标签的改名/删除请在原客户端操作(腾讯企业邮会级联清除邮件标签,插件不提供 破坏性操作);系统标签不可编辑;标签颜色优先使用服务器颜色,否则按名称生成。 旧版本(v1.4/v1.5)的本地标签关联仍会与服务器标签合并显示,不会丢失。 - 标签视图(v1.14):腾讯企业邮账号通过扫描各文件夹邮件 flags 过滤(60 秒内存缓存,刷新按钮可强制重查),
单次受「≤12 个文件夹、≤400 封」聚合窗口限制;腾讯服务器不支持标准
SEARCH KEYWORD,其它邮箱仍用 标准关键字搜索,Gmail 不支持标签视图。 - 内嵌图片(v1.14):正文内嵌小图仍随正文一次性返回;超过单图 512 KiB / 总量 3 MiB 的图片改为打开
正文后按需加载(单图 ≤8 MiB、单次 ≤16 MiB、≤200 张),加载完成前显示透明占位,失败时保留占位不报错。
被发送方错标为
application/octet-stream的内嵌图会按魔数识别为图片;无法识别的部件仍按普通附件处理。 - 回复/转发内嵌图片(v1.15):原邮件内嵌图(
cid:引用)随信重发,单图 ≤8 MiB、合计 ≤16 MiB、≤200 张; 远程http(s)图片仍由收件人客户端按既有规则决定是否加载。打开写信时若原图拉取失败(网络/服务器 问题),该图在发送时保持缺失且不阻塞发送。引用正文中的旧邮件若原本就缺少内嵌部件,插件无法补全。 - 按主题聚合(v1.16):只聚合「当前文件夹、已加载窗口内」主题归一化后相同的邮件,不跨文件夹/账号, 不做服务器端会话检索;搜索与智能视图自动退回平铺列表;会话展开状态不跨页面刷新持久化(开关本身 记忆在浏览器 localStorage)。聚合只影响列表展示,邮件总数/分页/加载更多语义不变。
- 列表标签(v1.17):列表行/会话组行每处最多显示 2 个标签胶囊,其余以
+N收起(悬停可看全部); 标签名称超长时省略号截断。会话组行的标签为组内邮件标签并集(最多 12 个)。 - 列表姓名(v1.16):已发送/草稿文件夹显示收件人(取第一位,多收件人见悬停提示);发件人缺失时回退 显示邮箱。
- 邮件列表行右键菜单(v1.14):未先打开邮件时,「回复/回复全部/转发」以列表摘要为种子(引用头正确、 引用正文为空);需要完整正文引用时先单击打开邮件或使用阅读区按钮。菜单仅覆盖单封邮件操作。
- 智能视图为只读聚合:星标视图扫描账号内前 12 个可选文件夹、通知视图优先服务器文件夹/收件箱启发式搜索、 标签视图在腾讯企业邮账号按服务器 flags 过滤(v1.14)、其它邮箱按服务器关键字/本地标签关联 (最多 12 个文件夹、400 封);超大邮箱的极旧邮件可能不在聚合窗口内。
- 缓存:正文缓存可能包含邮件内容(仅本机 0600 文件);共享电脑可在设置页清除缓存。 发信后新邮件、服务器端其他客户端做的改动,需等列表后台刷新/手动刷新后才可见。
No comments yet. Be the first to write one.