# Documents Activity Tracker 一个用于 Obsidian 的文档阅读时间追踪插件,帮助你了解在每个文档上花费的时间; 另附与文档追踪**相互独立**的两个计时器:正计时秒表与倒计时(倒计时 / 番茄钟 / 不规则周期)。 > **本仓库是重构版(v1.5.3)**:源码已从单体 `main.js` 重构为 `src/` 下的 TypeScript > 分层模块,存储由 JSON 改为 SQLite(sql.js / WASM)。行为与数据格式的对照说明见 > `docs/`,重构前的基线副本见 `docs/ROLLBACK.md`。 ## 功能特性 ### 文档追踪 - **手机前台计时**:移动端退出前台即结算阅读段,后台和锁屏不累计,也不填充热力图;回到前台可接着读。手动计时和倒计时保持各自的控制规则。 - **DeX/多窗口**:在该设备开启「移动端多窗口阅读(DeX)」,允许可见但失焦的窗口按桌面规则记录;可配合「分屏阅读」。真正隐藏、退出前台或锁屏仍停止。此开关只保存在本机,默认关闭;不根据屏幕宽度猜测设备模式。 - **实时计时**:按文档自动累计阅读时长,支持多轮会话 - **插件活动**:保存插件视图提供的完整活动标题,RSS 切换文章会记录标题变化;「插件视图计入阅读时长」默认开启,关闭后仍保留记录与热力图。 - **连续热力图**:连续高亮按整段选中,跨文章、文档和小时边界保留活动详情;真实未记录的空档断开,手动计时仍按完整轮次区分。 - **离开与空闲统一入口**:文档面板的「离开与空闲」按钮打开统一弹窗,设置离开窗口 / 空闲超时各自的「计入阅读 / 计入暂停 / 不记录」口径,并可逐段调整今天的离开记录。 - **四轨总热力图**:文档、插件视图、手动计时与倒计时使用同一时间轴的独立轨道;点击只展开对应来源的一段连续过程或完整一轮。暂停在轮次内部保留,累计时长按实际计时时段去重,四种来源同时覆盖也不会重复计算重叠时长。 - **过程预览**:总热力图、选中过程与当日明细共用来源筛选;连续停留均不超过 10 秒的文档或视图默认合并为可展开的切换摘要。时间范围显示到秒,详情就地展开;当日明细按来源收起,说明提示默认收起。 - **空闲现在真的记账**:以前 idle 超时后这段时间被直接丢弃,任何合计里都没有;现在它是一条独立通道(`idle`),按口径归类,并在热力图上有自己的颜色。 - **移动端后台**:按 Capacitor 官方文档同时订阅 `appStateChange`(Android 触发点是 onStop)与 `pause`/`resume`(Android onPause、iOS didEnterBackground),另加 Page Lifecycle 的 `freeze`/`resume` 与 `visibilitychange`;多个来源按状态去重。进程被系统回收时不会有任何事件,此时由心跳断层兜底:恢复后把断层补记为「离开」,不让它变成专注。 - **分屏阅读**:开始本轮后开启卡片下方的开关,失焦仍计入阅读;结束本轮、切换文档或进入后台后自动关闭。 - **暂停 / 继续**:按钮或命令手动控制 - **空闲检测**:可配置的空闲超时;失焦期间不做空闲判定(否则该功能 20 秒后即失效)。超时后的时间记进空闲通道,不再丢弃。 - **文件过滤**:黑/白名单,支持路径前缀与正则 ### 手动计时(独立) - 秒表式计时:开始 / 暂停 / 继续 / 停止 - 停止时填写「这段时间做了什么」,留空则只存时长 - 与文档追踪互不影响:不绑定文件、不受过滤规则影响、关闭文档追踪后照常可用 - **毫秒精度**:正计时与倒计时的计算与落库都以毫秒为准(`durationMs`),秒只是它的投影;只有正计时的读数有「显示毫秒」开关,打开后刷新加快到 0.1 秒,读数形如 `00:41.208`。 - **未保存计时可恢复**:正计时与倒计时每秒把「开始时间 + 净已计时」写进 localStorage;重载、崩溃或退出后,侧栏对应面板会显示一张恢复卡(命令面板里也有「恢复未保存的计时」)。恢复出来的是**暂停**态,累计值等于快照那一刻 —— 应用关着的时间不会凭空算进去。 ### 倒计时(与正计时完全独立) 三种模式共用一套分段模型 —— 一次运行就是一串固定长度的分段,跑完一段自动进下一段: - **倒计时**:单段,例如 25 分钟 - **番茄钟**:专注 + 休息为一个周期;可单周期,也可重复多轮(末轮休息保留) - **不规则周期**:若干不等长的段依次执行;设置里每行右侧的「复制这一段」用于单周期复用 行为要点: - 方案在**开始的那一刻**解析并存进状态,运行中改设置不影响正在跑的这一次 - 跑完最后一段自动收尾:默认直接保存(备注为空)并给出提示;开关打开时弹出备注框, **取消也保存**(跑完的那段不该因为没写备注而消失) - 倒计时自成一账:独立状态、独立面板、独立记录与统计、独立数据表;正计时的面板与合计里 不会出现倒计时的任何一条,反之亦然 - 与正计时可以同时运行、各自暂停,互不冻结 ### 侧栏 一个正式的侧栏视图(`ItemView`),面板标签切换: - **文档追踪面板**:今日阅读、今日总时长、今日离开 / 暂停 / 空闲、本周阅读与本周总时长;当前文件的操作按钮;「离开与空闲」弹窗入口;今日各文档列表。统计来自同一份账本(`core/account.ts`),日与周只差窗口长度。 - **手动计时面板**:计时控制、当日热力图(按小时 × 分钟定位)、当日记录列表 - **倒计时的面板**:圆环与剩余时间、分段方案(当前段高亮)、当日合计与记录列表 ### 三个总开关 设置中可分别关闭「文档追踪」「手动计时」与「倒计时」;关闭文档追踪会同时停止计时并保存 当前会话,关闭另外两个会丢弃正在运行的那一轮。 ## 安装 从仓库根目录复制 `manifest.json`,并从 `dist/` 复制以下三个文件到 `/.obsidian/plugins/documents-activity-tracker/`: ``` main.js styles.css sql-wasm.wasm ← SQLite 由 sql.js(WASM) 提供,缺这个文件插件会降级为 「不落盘」并在界面与通知里明确告知,不会假装已保存 ``` 然后在 Obsidian 设置中启用插件并重新加载。 ## 数据存储 - 记录存于 **SQLite**,路径由设置的「自定义数据文件路径」换扩展名派生 (如 `Components/History/readTimeHistory.json` → `Components/History/readTimeHistory.sqlite`)。 - **不迁移历史数据**:新库从空开始,只记录从此以后的使用。旧 JSON 数据文件**既不读取 也不改写**,原样留在磁盘上由用户自行处置。 - 设置本身仍存于插件目录的 `data.json`(Obsidian 惯例)。 - 表:`events`(事件流,唯一真相)、`records`(物化统计)、`manual_sessions`(正计时)、 `countdown_sessions`(倒计时,含模式、计划长度、是否跑完与分段方案)。两张会话表都有 `duration_ms` 与整秒的 `duration_seconds`。 后两张表分开是有意的:两本账各自查询,任何一条都不会混进对方的合计。 - 库结构版本 5;旧库在启动时自动补建 `countdown_sessions` 与 `duration_ms`(旧记录按整秒×1000 读),既有数据的秒值不动。 ## API 兼容性 最低支持 Obsidian 1.7.2。开发依赖固定为官方 `obsidian@1.7.2`,不再内置新版 API 类型声明。`bun run lint:api` 使用官方 `obsidianmd/no-unsupported-api` 规则检查源码;检查与发布构建均包含此步骤。 ## 开发 ```bash bun run check # tsc --noEmit + bun test bun build.ts # 开发构建 -> dist/ bun build.ts --prod # 生产构建 bun build.ts --watch # 监听 src/ 与 styles/ 增量重建 # 部署目标由调用方给定 —— 仓库里不硬编码本地 vault 路径 RTT_VAULT_PLUGIN_DIR=/path/to/vault/.obsidian/plugins/documents-activity-tracker \ bun build.ts --deploy ``` ### 分层 ``` core/ 纯逻辑,零环境依赖(无 window/document/localStorage),可离线单测 data/ SQLite 存储与适配器;服务层只依赖 HistoryStore 接口 services/ 计时服务(文档 / 手动),不 import obsidian 运行期值 ui/ 视图与 Obsidian 事件绑定(唯一在运行期 import obsidian 值的一层) main.ts 装配,不放业务判断 ``` ### 样式 `styles/` 下 13 个模块按 `build.ts` 里的顺序逐字节拼接。拼接结果对既有模块有 md5 守门(`CSS_REFERENCE_MD5`),新增样式必须放进 `CSS_MODULES_EXTRA`,不得改动既有模块。 ## 许可证 MIT License