待办支持附件(ATTACH) #44

Closed
opened 2026-11-04 00:53:02 +00:00 by laoyang · 3 comments
Owner

现状

grep ATTACH = 0,LocalEvent 无 attachments 字段。日程与待办都没有附件能力。

需要做的事

  1. 表加 attachments(TEXT,存 JSON 数组)
  2. IcsUtil 读写 ATTACH(URI 形式 ATTACH;FMTTYPE=...:<url>)
  3. 待办弹层增加"添加附件":选文件 → 上传到服务器(CalDAV 附件需服务器支持
    PUT 到集合的资源路径,或走外部链接 URI)
  4. 列表/详情显示附件(可点开)

关键风险 ⚠️

CalDAV 附件依赖服务器支持。 群晖的 CalDAV 实现未必支持 ATTACH 的
托管上传(RFC 8607 的 managed attachments 是较新扩展)。
需先探测服务器能力:PROPFIND 查 supported-report-set /
直接用 ATTACH 存外部 URL(HTTP 链接)——后者最稳妥。

建议方案

先做"外部链接附件"(ATTACH 存 URL,不托管文件),
风险低、能覆盖"贴个资料链接"的主要场景。
托管上传留待确认服务器支持后再做。

验收标准

  • 给待办添加一个 URL 附件,服务器 .ics 里出现 ATTACH:<url>
  • 打开待办能看到并点击打开附件

为什么单独建单

附件是 ATTACH 协议能力,与子任务、提醒、字段扩展均无重叠

## 现状 `grep ATTACH` = 0,`LocalEvent` 无 `attachments` 字段。日程与待办都没有附件能力。 ## 需要做的事 1. 表加 `attachments`(TEXT,存 JSON 数组) 2. `IcsUtil` 读写 `ATTACH`(URI 形式 `ATTACH;FMTTYPE=...:<url>`) 3. 待办弹层增加"添加附件":选文件 → 上传到服务器(CalDAV 附件需服务器支持 `PUT` 到集合的资源路径,或走外部链接 URI) 4. 列表/详情显示附件(可点开) ## 关键风险 ⚠️ **CalDAV 附件依赖服务器支持。** 群晖的 CalDAV 实现未必支持 `ATTACH` 的 托管上传(RFC 8607 的 managed attachments 是较新扩展)。 需先探测服务器能力:`PROPFIND` 查 `supported-report-set` / 直接用 `ATTACH` 存外部 URL(HTTP 链接)——后者最稳妥。 ## 建议方案 **先做"外部链接附件"**(ATTACH 存 URL,不托管文件), 风险低、能覆盖"贴个资料链接"的主要场景。 托管上传留待确认服务器支持后再做。 ## 验收标准 - 给待办添加一个 URL 附件,服务器 `.ics` 里出现 `ATTACH:<url>` - 打开待办能看到并点击打开附件 --- ### 为什么单独建单 附件是 ATTACH 协议能力,与子任务、提醒、字段扩展均无重叠
Author
Owner

方案变更:改为「内联 base64」标准实现(2026-11-04 用户决策 + 实测)

用户明确:不做"自己传文件贴链接"的方案,只要协议支持就按标准协议实现。

查证结论(分层)

  1. iCalendar RFC 5545 原生支持附件:ATTACH 是核心属性,明确可用于 VTODO。
    两种形式:URI(外部链接)和内联 base64(ATTACH;FMTTYPE=...;ENCODING=BASE64;VALUE=BINARY:<base64>)。
    内联 base64 就是 .ics 文本的一部分,任何符合 RFC 5545 的服务器都能存,不需要服务器特殊能力。

  2. RFC 8607 托管附件(calendar-managed-attachments)是另一层扩展,需要服务器显式支持。
    之前探测到的"不支持"是这层,群晖没有。

实测(群晖 synccalendartest)

  • PUT 带 ATTACH;FMTTYPE=text/plain;ENCODING=BASE64;VALUE=BINARY:<base64> 的待办 → 201
  • GET 回原文 → ATTACH 行原样保留(未丢弃、未改写)
  • 结论:群晖作为普通 CalDAV 服务器,接受并保留内联 base64 附件 ✅

实现方案(按标准协议)

  • 附件作为 VTODO 的 ATTACH 行,内联 base64 编码,随 .ics 一起 PUT/同步
  • 不依赖服务器托管能力,不需要自己找地方传文件贴链接

固有代价(需知悉)

内联 base64 会让 .ics 变大,且每次改写该待办都要重传全部附件数据
(这正是 RFC 8607 要解决的痛点,但我们服务器不支持 8607,只能接受)。
对策:附件数量/大小做了合理限制后,代价可控。

## 方案变更:改为「内联 base64」标准实现(2026-11-04 用户决策 + 实测) 用户明确:不做"自己传文件贴链接"的方案,只要协议支持就按标准协议实现。 ### 查证结论(分层) 1. **iCalendar RFC 5545 原生支持附件**:`ATTACH` 是核心属性,明确可用于 VTODO。 两种形式:URI(外部链接)和**内联 base64**(`ATTACH;FMTTYPE=...;ENCODING=BASE64;VALUE=BINARY:<base64>`)。 内联 base64 就是 .ics 文本的一部分,**任何符合 RFC 5545 的服务器都能存**,不需要服务器特殊能力。 2. **RFC 8607 托管附件**(`calendar-managed-attachments`)是另一层扩展,需要服务器显式支持。 之前探测到的"不支持"是这层,群晖没有。 ### 实测(群晖 synccalendartest) - PUT 带 `ATTACH;FMTTYPE=text/plain;ENCODING=BASE64;VALUE=BINARY:<base64>` 的待办 → **201** - GET 回原文 → **ATTACH 行原样保留**(未丢弃、未改写) - 结论:群晖作为普通 CalDAV 服务器,**接受并保留内联 base64 附件** ✅ ### 实现方案(按标准协议) - 附件作为 VTODO 的 `ATTACH` 行,内联 base64 编码,随 .ics 一起 PUT/同步 - 不依赖服务器托管能力,不需要自己找地方传文件贴链接 ### 固有代价(需知悉) 内联 base64 会让 .ics 变大,且**每次改写该待办都要重传全部附件数据** (这正是 RFC 8607 要解决的痛点,但我们服务器不支持 8607,只能接受)。 对策:附件数量/大小做了合理限制后,代价可控。
Author
Owner

验收记录

提交:d9db8b4

方案确认 + 实测

按用户决策,走标准协议内联 base64(非托管上传、非外部链接)。

实测群晖:

  • PUT 带 ATTACH;FMTTYPE=...;ENCODING=BASE64;VALUE=BINARY:<b64> → 201
  • GET 原样保留(4558 字符整行、含中文文件名「报告.pdf」都不丢、不截断)

改了什么

  • ATTACH 内联 base64 读写(IcsUtil attachLine/parseTodos)
  • 数据层 attachments JSON([{name,mime,base64}])
  • 弹层「附件」区:文件选择器 → 读二进制 → base64 → 随待办保存
  • rewriteTodoFields touchAttach 哨兵:不编辑附件时保留服务器原值

限制

最多 3 个附件、单文件 ≤5MB(内联 base64 膨胀,限制防爆)。

遗留

真机文件选择器上传未验(设备离线)。需人工验:选文件→保存→另一客户端看到附件。

## 验收记录 提交:`d9db8b4` ### 方案确认 + 实测 按用户决策,走**标准协议内联 base64**(非托管上传、非外部链接)。 实测群晖: - PUT 带 `ATTACH;FMTTYPE=...;ENCODING=BASE64;VALUE=BINARY:<b64>` → 201 - GET 原样保留(4558 字符整行、含中文文件名「报告.pdf」都不丢、不截断) ### 改了什么 - `ATTACH` 内联 base64 读写(IcsUtil attachLine/parseTodos) - 数据层 `attachments` JSON([{name,mime,base64}]) - 弹层「附件」区:文件选择器 → 读二进制 → base64 → 随待办保存 - `rewriteTodoFields` touchAttach 哨兵:不编辑附件时保留服务器原值 ### 限制 最多 3 个附件、单文件 ≤5MB(内联 base64 膨胀,限制防爆)。 ### 遗留 真机文件选择器上传未验(设备离线)。需人工验:选文件→保存→另一客户端看到附件。
Author
Owner

真机验证结果(2026-11-04 19:00,设备 5NC0225B27004718)

设备连上后补做了本批(#43 #44 #45)的真机验证。

#45 待办投射到日历 —— ✅ 全部通过

服务器写入一条「今天 DUE」的待办后:

验证项 结果
时间轴日志 待办 0 条 → 待办 1 条(同步后正确识别)
界面渲染 [144,1398][1004,1442] 今天截止验证
位置正确 在「11月4日 三」下方、07:00 刻度之上(待办区在时间轴顶部)
勾选交互 点勾选圈 → 待办完成成功 id=750
自动刷新 勾选后 待办 1 条 → 0 条,界面消失
完成态过滤 已完成的不再显示(待办区是否还在: False)

#43 子任务 —— ✅ 全部通过

服务器写入父任务 + 子任务(RELATED-TO)后:

验证项 结果
同步 个人成长待办:REPORT 4 个资源,解析 4 条
父任务显示 [202,796][437,851] 父任务验证
父任务进度 副行 无截止时间 · 群晖:个人成长 · 0/1
子任务缩进 [277,985][571,1040] ∟ 子任务验证(x 从 202→277,缩进 75px)
连线符 ∟ 正确显示
级联完成 点父任务 → 待办完成成功 id=751 + id=752 + 级联完成 1 个子任务(父 verify43-parent)

#44 附件 —— ⚠️ 部分通过

验证项 结果
弹层字段 父任务(y=1810)、附件(y=1905) 均渲染
「添加文件」按钮 存在且可点
文件选择器唤起 ✅ 日志出现 filePicker/FileMgr 记录,选择器正常打开
完整上传链路 ❌ 未验(无法用自动化在系统选择器里选文件)

清理

测试数据已全部删除(verify45-today-todo / verify43-parent / verify43-child,均 204)。

## 真机验证结果(2026-11-04 19:00,设备 5NC0225B27004718) 设备连上后补做了本批(#43 #44 #45)的真机验证。 ### #45 待办投射到日历 —— ✅ 全部通过 服务器写入一条「今天 DUE」的待办后: | 验证项 | 结果 | |---|---| | 时间轴日志 | `待办 0 条` → `待办 1 条`(同步后正确识别) | | 界面渲染 | `[144,1398][1004,1442] 今天截止验证` | | 位置正确 | 在「11月4日 三」下方、07:00 刻度之上(待办区在时间轴顶部) | | 勾选交互 | 点勾选圈 → `待办完成成功 id=750` | | 自动刷新 | 勾选后 `待办 1 条` → `0 条`,界面消失 | | 完成态过滤 | 已完成的不再显示(`待办区是否还在: False`) | ### #43 子任务 —— ✅ 全部通过 服务器写入父任务 + 子任务(RELATED-TO)后: | 验证项 | 结果 | |---|---| | 同步 | `个人成长待办:REPORT 4 个资源,解析 4 条` | | 父任务显示 | `[202,796][437,851] 父任务验证` | | 父任务进度 | 副行 `无截止时间 · 群晖:个人成长 · 0/1` | | 子任务缩进 | `[277,985][571,1040] ∟ 子任务验证`(x 从 202→277,缩进 75px) | | 连线符 | `∟` 正确显示 | | **级联完成** | 点父任务 → `待办完成成功 id=751` + `id=752` + `级联完成 1 个子任务(父 verify43-parent)` | ### #44 附件 —— ⚠️ 部分通过 | 验证项 | 结果 | |---|---| | 弹层字段 | 父任务(y=1810)、附件(y=1905) 均渲染 | | 「添加文件」按钮 | 存在且可点 | | 文件选择器唤起 | ✅ 日志出现 `filePicker/FileMgr` 记录,选择器正常打开 | | **完整上传链路** | ❌ 未验(无法用自动化在系统选择器里选文件) | ### 清理 测试数据已全部删除(verify45-today-todo / verify43-parent / verify43-child,均 204)。
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: laoyang/FamilyBook#44