【免费下载链接】atomic-calendar-revive
An advanced calendar card for Home Assistant Lovelace.
Atomic Calendar Revive 是一款为 Home Assistant Lovelace 打造的进阶日历卡片,支持 Google Calendar 与 CalDAV 日历。它最强大的地方在于"事件处理管道"(pipeline):从拉取原始事件,到过滤、多日拆分、去重、排序、数量截断,全部在一条清晰的流水线上完成。本文将带你读懂这条管道的设计与实现原理,帮你理解maxDaysToShow、hideDuplicates、sortBy等配置项背后到底发生了什么。
一、管道全景:一次数据流的旅程 🗂️
管道的核心实现在 src/lib/pipeline.ts,对外暴露三个入口函数,分别服务于卡片的三种展示模式:
| 入口函数 | 服务视图 | 说明 |
|---|---|---|
fetchEventModeEvents | 事件列表模式 | 计算日期窗口 → 拉取 → 处理 |
fetchPlannerEvents | 计划(Planner)模式 | 周视图窗口 → 拉取 → 处理 |
fetchRawEvents+processEvents | 日历月视图 | 拉取整月数据后处理 |
各视图的调用点分别在 src/lib/views/EventView.ts、src/lib/views/PlannerView.ts 与 src/lib/views/month-grid.ts。
处理主函数processEvents的完整流程只有一屏(src/lib/pipeline.ts#L197-L229),可以概括为五步:
- 逐事件过滤:不通过则直接丢弃(
continue) - 多日事件拆分:
expandMultiDay把跨天事件切成"每天一份" - 二次过滤:
hideFinishedEvents隐藏已结束事件 - 去重:
hideDuplicates合并同名同时间的重复事件 - 排序 + 截断:
sortEvents排序后用maxEventCount限制数量
二、第一步:按日历实体并行拉取原始事件
fetchRawEvents(src/lib/pipeline.ts#L259-L297)为每个配置的日历实体构造一个calendars/{entity}?start=...&end=...的 API 请求,并用Promise.all并行执行。两个细节值得注意:
- 每个实体可以有自己的时间窗口:实体级
maxDaysToShow会收窄请求的end时间,避免为"只看今天"的日历拉取整周数据 - 失败隔离:某个日历请求失败只会被记入
failed数组,不会让整张卡片挂掉,视图层会据此给出降级提示
三、核心:一条"过滤漏斗"决定哪些事件能上卡 🎯
拉到的原始事件会依次穿过 9 道过滤器(src/lib/pipeline.ts#L200-L215),任何一道不过即被丢弃:
| 过滤器 | 对应配置项 | 作用 |
|---|---|---|
passesAllDayBeforeWindow | startDaysAhead | 剔除在展示窗口开始前就已结束的整天事件 |
passesDeclined | showDeclined | 是否显示已被拒绝的受邀事件 |
passesAllDayHidden | showAllDayEvents | 是否隐藏整天事件 |
passesBlocklist | blocklist | 标题正则黑名单,命中即丢弃 |
passesBlocklistLocation | blocklistLocation | 地点正则黑名单 |
passesAllowlist | allowlist | 标题正则白名单,不命中即丢弃 |
passesAllowlistLocation | allowlistLocation | 地点正则白名单 |
passesMaxDaysWindow | maxDaysToShow | 超出天数窗口的未来事件被剔除 |
passesTimeOfDayWindow | startTimeFilter/endTimeFilter | 只保留落在指定时刻区间内开始的事件 |
几个容易踩坑的设计点:
- 黑白名单匹配的是"原始标题"
rawTitle,而不是被titleReplace改写后的展示标题。这样即使你用titleReplace把Dinner = Sushi前缀抹掉了,allowlist: '^Dinner = '依然能命中——这个行为有专门的回归测试保护(src/tests/pipeline.test.ts#L60-L80) - Calendar 模式跳过窗口过滤:
passesAllDayBeforeWindow和passesMaxDaysWindow对月视图直接放行(src/lib/pipeline.ts#L47-L51),因为月网格需要展示当月所有格子 maxDaysToShow的实体级覆盖:实体级maxDaysToShow优先于全局值,这正是"多卡片滑动布局"能各自看不同天的原理
四、多日事件拆分:showMultiDay 的幕后逻辑
开启showMultiDay后,跨天事件会在过滤器通过后被expandMultiDay展开(src/lib/pipeline.ts#L145-L153):
EventClass.split()(src/lib/event.class.ts#L273-L295)按天深拷贝事件,打上addDays、_isFirstDay、_isLastDay标记,让每一天都有一份"部分事件"- 拆分后还要过一遍可见性窗口:
computeVisibilityWindow按模式算出起止范围,只有落在窗口内的部分才会保留(Event/Planner 模式);Calendar 模式则保留全部 - 实体级
showMultiDay可以独立于全局开关生效,方便"每张卡片只看一天"的滑动布局
五、去重原理:hideDuplicates 如何合并重复事件
applyHideDuplicates(src/lib/pipeline.ts#L157-L180)采用经典的哈希键去重两遍扫描:
- 以
标题|开始时间|结束时间拼接为键。相同的事件(比如同一个事件同时同步到了两个日历实体)只会保留第一条,其余来源的日历名被追加进calendars列表 - 第二遍把收集到的来源名用
,拼接后写回事件的originName,卡片上这条事件仍会显示所有来源日历,信息不丢失
这正是官方文档描述的行为:"based on summary, start time and end time"(见 docs/configuration/main.rst 第 35 行)。对应测试见 src/tests/pipeline.test.ts#L141-L162。
六、排序规则:按天分桶 + 两种排序策略
排序逻辑独立在 src/common/sort_events.ts 中,分四步:
- 全局按
startDateTime升序,保证"天"的天然顺序 - 按
YYYY-MM-DD分桶,逐天处理:- 整天事件按标题字母序排列;
allDayBottom: true时反转并放到当天底部(src/common/sort_events.ts#L24-L29) sortBy: start:普通事件按开始时间升序sortBy: milestone(里程碑模式):正在运行的事件排最前,其余按"距当前时间最近者"排序,已结束事件沉底(src/common/sort_events.ts#L37-L74)
- 整天事件按标题字母序排列;
- 天内合并:整天事件与时刻事件按
allDayBottom决定上下位置 - 跨天合并:各天数组依序拼接,天内的自定义排序不被破坏
这个"先分桶、桶内精排、再按桶序合并"的结构,避免了全局比较器里处理跨天、跨类型事件时的复杂性。
七、数量截断:maxEventCount 与 softLimit 的软硬两级限制
最后的applyEventCountLimit(src/lib/pipeline.ts#L182-L193)实现了"硬限制 + 软缓冲":
- 只设
maxEventCount:超出部分直接截断,截掉的数量记入hidden,卡片上显示 "x hidden" - 同时设
softLimit:事件数不超过maxEventCount + softLimit时不截断——避免"只多出 1 条就截断"造成的观感跳动(说明见 docs/configuration/event.rst 第 13 行) - 排序发生在截断之前,所以被保留的永远是时间上最靠前的事件
八、三种模式下的行为差异一览 📅
| 行为 | Event 模式 | Calendar 模式 | Planner 模式 |
|---|---|---|---|
| 日期窗口 | maxDaysToShow+startDaysAhead | 整个可见月份 | plannerDaysToShow(周窗口) |
| 窗口前整天事件 | 剔除 | 保留 | 剔除 |
maxDaysToShow过滤 | 生效 | 跳过 | 生效(用plannerDaysToShow作基准) |
showPastDaysOfWeek | 窗口锚定到本周一(firstDayOfWeek感知) | — | — |
周窗口的对齐算法在getEventModeStart(src/lib/pipeline.ts#L35-L43):用当前星期几减去firstDayOfWeek得到偏移量,负数时补 7,从而把窗口起点回退到本周第一天。
九、测试如何守护这条管道
管道是纯函数式的数据加工层,因此拥有非常完整的测试套件 src/tests/pipeline.test.ts(540+ 行):用vi.useFakeTimers固定系统时间,逐一覆盖黑名单/白名单、hideDuplicates合并来源名、softLimit边界、单日窗口不泄漏次日整天事件、showPastDaysOfWeek的周日/周一周界回归等场景,是理解每个过滤器预期行为最快的"活文档"。
十、相关配置项速查
- 主配置:
maxDaysToShow、maxEventCount、hideDuplicates、showDeclined、showAllDayEvents、hideFinishedEvents、sortBy、allDayBottom(docs/configuration/main.rst) - 实体级过滤:
blocklist、allowlist、startTimeFilter、endTimeFilter、showMultiDay(docs/configuration/entity.rst) - 事件模式扩展:
softLimit、showHiddenText、hiddenEventText(docs/configuration/event.rst) - 默认值定义:src/defaults.ts
理解了这条管道,你就能解释卡片上大多数"为什么这条事件没显示 / 为什么排在这个位置"的问题——答案几乎都藏在processEvents那 30 行主干里。
【免费下载链接】atomic-calendar-revive
An advanced calendar card for Home Assistant Lovelace.
相关推荐
github_preprocessing.py源码逐行剖析:github-code去重与质量过滤的实现原理
github_preprocessing.py源码逐行剖析:github code去重与质量过滤的实现原理 github_preprocessing.py 是
gitinspector国际化实现:如何为项目添加新的语言翻译
gitinspector国际化实现:如何为项目添加新的语言翻译 gitinspector是一款强大的git仓库统计分析工具,支持多语言国际化功能。本文将详细介绍
人工智能大模型AI Agent多智能体金融科技后端前端Machine-Learning-for-Algorithmic-Trading-Second-Edition_Original中的100+Alpha因子库:构建盈利策略的秘密武器
Machine Learning for Algorithmic Trading Second Edition_Original中的100+Alpha因子库:构
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考