` **之前**,
+ 某些布局下会把表单控件的行内关系弄乱;
+ · 而**页面照样 200、照样能用** ⇒ 只有"查正文里有没有 PHP 错误"的断言能抓到。
+ ⇒ 教训:可选参数一律 `?? ''` 兜底,**不要靠 `if (!$x)` 当存在性判断**
+ (读它那一刻 Warning 就已经产生了)。
+ ⚠️ 我的 v72 前端探针当时 47/0 全绿却没抓到它 —— 因为它只查 JS 报错、
+ 不查服务端正文。已给探针补上这条(见 `_fb_v72_ui.py` 的「PHP 正文干净」)。 */
+$cpId = (string)($cpId ?? '');
+
+if ($cpId === '') {
+ // 表单模式用 name 派生;独立模式没有 name ⇒ 调用页必须给 id
+ $cpId = 'cp_' . preg_replace('/[^A-Za-z0-9_]/', '_', $cpName !== '' ? $cpName : 'color');
+}
+
+/* ⚠️ 初始值本身**不是**合法 hex 时(老数据、手工改库),退回落日色而不是把
+ `background: xxx` 直接拼进 style —— 那样会把整条样式声明弄坏,
+ 旁边几个属性一起失效(用户看到的是"色块变成一个方块")。 */
+if (!preg_match('/^#[0-9A-Fa-f]{6}$/', $cpValue)) {
+ $cpValue = '#4A7C82';
+}
+
+$cpDataAttr = '';
+foreach ($cpData as $k => $v) {
+ $cpDataAttr .= ' data-' . e((string)$k) . '="' . e((string)$v) . '"';
+}
+?>
+>
+
+
+ id="= e($cpId) ?>" value="= e($cpValue) ?>">
+ `(原生取色控件)。
+
+ 那是**没改完的旧写法**,同时踩了两件事:
+ ① **用户点名要去掉的就是它**(「选颜色那个地方点了之后是弹出 Windows 的
+ 一个颜色选择框,这个不好」)—— 留着它等于需求没做;
+ ② CSS/JS 是照「按钮 + 内部小色点 + hex 文本」设计的:
+ `.cp-trigger` 是 `inline-flex` 胶囊、`.cp-trigger-dot` / `.cp-trigger-hex`
+ 是它的子元素、模态框靠 `[data-cp-open]` + `[data-cp-target]` 打开并回填。
+ 而原生 `` **不能有子元素**,也没有这两个属性
+ ⇒
+ · `syncTrigger()` 找不到 `.cp-trigger-dot/hex` ⇒ 改完色**按钮上不刷新**;
+ · `document.querySelectorAll('[data-cp-open]')` 一个都取不到
+ ⇒ 模态框**永远打不开**,用户点到的是浏览器原生取色框。
+ ⇒ 现在输出的是 `
\ No newline at end of file
diff --git a/templates/_color_picker_modal.php b/templates/_color_picker_modal.php
new file mode 100644
index 0000000..24732b7
--- /dev/null
+++ b/templates/_color_picker_modal.php
@@ -0,0 +1,253 @@
+`(用户明确不要)
+ *
+ * 用户原话:「选颜色那个地方点了之后是弹出 Windows 的一个颜色选择框,这个不好,
+ * 我们把它改成内置的……不要弹 Windows 的,那个一个体验也不好,
+ * 另外一个也不好看。」
+ *
+ * `` 的外观与交互**完全由操作系统决定**:Windows 上弹出的是
+ * 那个带色轮和 HSV 滑条的老式对话框,macOS 上是另一套,手机上是第三套。
+ * ⇒ 站点自己控制不了,也无法与本站的圆角/配色对齐。所以整站不再用它取色。
+ *
+ * ⚠️ **保留自定义 hex 输入**:预置色板只有 16 个,用户想要某个精确颜色时
+ * 手输 `#RRGGBB` 是最快的路(也让"从别处复制一个色值"变得可能)。
+ * 输入非法时不提交非法值 —— 按钮保持 disabled,输入框边框变红。
+ *
+ * ⚠️ **键盘与焦点**:打开时焦点进模态框、关闭时焦点回触发按钮。
+ * 不这么做的话,键盘用户关掉后焦点掉到 `body`,Tab 会从页首重新开始。
+ *
+ * ⚠️ **焦点陷阱(Tab 循环)**:模态框开着时 Tab 不许跑到背后的页面上 ——
+ * 这是 `aria-modal="true"` 的语义要求,也是"看起来能点但其实点不到"的体验修复。
+ *
+ * @var array $cpPalette 预置色板('#RRGGBB' 列表)
+ * @var string $cpModalTitle 可选,标题
+ */
+$cpPalette = array_values(array_filter((array)($cpPalette ?? []), 'is_string'));
+$cpModalTitle = (string)($cpModalTitle ?? '选个颜色');
+if (!$cpPalette) {
+ // 没有色板就没有"点一下选中"的价值,只剩 hex 输入 —— 但仍然比系统框好看
+ $cpPalette = ['#4A7C82'];
+}
+?>
+
+
+
+ = e($cpModalTitle) ?>
+ ✕
+
+
+
+
+
+ 日程
+ #4A7C82
+
+
+
+
+
+
+
+
+
+
+ #
+
+ 要 6 位十六进制(0-9 / A-F)
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/templates/calendar/_settings_menu.php b/templates/calendar/_settings_menu.php
index b7852c9..6472f71 100644
--- a/templates/calendar/_settings_menu.php
+++ b/templates/calendar/_settings_menu.php
@@ -23,11 +23,21 @@
* 三处都是"☰ → 一串列表"。
*
* 四行(顺序是用户点名的):
- * ① 显示设置 → `calendar/display`(默认视图 + 显示哪些本子)★ 本批新增的独立页
- * ② 日历本管理 → `calendar/calendars`(改名/换色/共享/导入)
+ * ① 显示设置 → **v72 起与「日历本管理」合并**:两行都落到 `calendar/calendars`
+ * (用 `#cal-books` 锚点区分落点,见下)
+ * ② 日历本管理 → `calendar/calendars`(显隐 + 改名/换色/共享/导入,全在一页)
* ③ 订阅管理 → `calendar/calendars#caldav`(只读订阅进来的外部日历)
* ④ DAV 口令 → `settings/me#dav`(日历 + 通讯录共用那一把)
*
+ * 🔴 v72(用户 2026-10-21):「日历设置里面日历本显示和日历管理可以放在一起啊。
+ * 就是把日历本全都列出来,然后显示不显示,加个复选框就行了。」
+ * ⇒ ① 与 ② **现在是同一页**。但**四行都留着**,原因:
+ * · 用户 2026-10-15 第 6 条明确点名"下拉就是这四行",少一行他会以为功能没了;
+ * · 两行落到同一页、用锚点定位到各自那一段 —— 点"显示设置"落在清单、
+ * 点"日历本管理"也落在清单,但**用户想找的东西就在那儿**,不用再跳。
+ * ⚠️ 千万别因为"合并了"就把菜单砍成三行:那是**把用户的入口拿走**,
+ * 而他这次要的是"放一起",不是"少一个入口"。
+ *
* 用法(require 前 set 好):
* $setCurrent 当前页 key('view'|'books'|'dav')用来高亮;
* '' 也行(比如从旅程/观影页挂过来时没有当前项)
@@ -57,7 +67,7 @@ $setIcon = static function (string $name): string {
@@ -65,109 +121,201 @@ $canWrite = \App\Auth::canWrite();
-
-
我自己的
-
-
还没有日历本。
-
-
-
-
- | 名字 | 颜色 | 共享给 | 操作 |
-
-
-
-
- |
- = e((string)$c['name']) ?>
-
- 系统预置
+ 套进大表单里(套了就整个失效)。 */ ?>
+ |
-
-
- = e((string)$c['color']) ?>
- |
-
-
-
- 不共享
-
-
-
- = person_chip($s) ?>
- = ($s['perm'] ?? '') === 'write' ? '可写' : '只读' ?>
- 里,现在 撤了,这里就地给个按钮。 */ ?>
-
-
-
-
-
-
-
-
+
+ $c['owner_name'] ?? '',
+ 'my_nickname' => $c['owner_nick'] ?? '',
+ ]);
+ ?>
+ = e($ownerName) ?> 共享给我
+
+ = $perm === 'write' ? '可写' : '只读' ?>
+
+
+
+
+
+
+
+ 不可修改
+
+
共享设置
+ data-cal-id="= $cid ?>"
+ data-cal-name="= e((string)$c['name']) ?>">共享
+
+ 导出
+
+ 编辑
+
- |
-
-
-
- |
-
-
-
-
+
+
+
+
+
+
+
+ 已共享给
+
+ = person_chip($s) ?>
+ = ($s['perm'] ?? '') === 'write' ? '可写' : '只读' ?>
+
+
+
+
+
+
+
+
+
+
+
+ 观影、旅行、有截止时间的任务会自动出现在这里,不用另建本子。
+
+
+
-
+
+
+
+
+
+
默认打开时看
+ 打开日程页默认落在哪个视图
+
+ 只能导进自定义本 —— 观影/旅行两本是派生的,塞进去就成了孤儿日程。 */ ?>
导入 ics
@@ -191,17 +339,11 @@ $canWrite = \App\Auth::canWrite();
- .files` 始终是空的 → 带 required 的提交会被
- 浏览器原生校验拦下,弹「请选择一个文本。」(用户报的就是这句)。 */
- $sdName = 'ics'; $sdId = 'im-file'; $sdAccept = '.ics,text/calendar';
- $sdMultiple = false; $sdRequired = true; $sdStage = false;
- require __DIR__ . '/../_stage_drop.php'; ?>
+
+
@@ -211,12 +353,7 @@ $canWrite = \App\Auth::canWrite();
-
+
CalDAV 订阅
@@ -241,42 +378,13 @@ $canWrite = \App\Auth::canWrite();
-
-
-
别人共享给我的
-
-
- | 名字 | 谁的 | 我的权限 |
-
-
-
- | = e((string)$c['name']) ?> |
-
- = e(person_name(['name' => $c['owner_name'] ?? '', 'my_nickname' => $c['owner_nick'] ?? ''])) ?> |
- = ($c['perm'] ?? '') === 'write' ? '可写' : '只读' ?> |
-
-
-
-
-
-
-
-
` 兜底。12 本 ⇒ 整页 5.9 MB / 近万个 input
- ⇒ 浏览器与密码管理器逐个扫描,慢得能感觉到。
- 收成一份之后:**一整套选人组件**(1 份 JSON + 1 份 noscript),
- 与日历本数无关 —— 再多本日历页也不会再变大。
-
- ⚠️ 选人组件是**单选**(`$ppMode='single'`):行上按钮被点开时,用
- `window.ppSetSelected('person_id', 0, panel)` 把它重置成"未选",
- 再让用户挑 —— 否则上一次挑的人会留在表单里(看着像"已经选好了")。 */ ?>
+ ⇒ 浏览器与密码管理器逐个扫描,慢得能感觉到。 */ ?>
@@ -311,9 +419,7 @@ $canWrite = \App\Auth::canWrite();
@@ -354,3 +460,91 @@ $canWrite = \App\Auth::canWrite();
})();
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/templates/calendar/display.php b/templates/calendar/display.php
deleted file mode 100644
index 0d45c56..0000000
--- a/templates/calendar/display.php
+++ /dev/null
@@ -1,99 +0,0 @@
-
-
-
显示设置
-
默认看哪个视图、日历上显示哪几本
-
-
-
-
-
\ No newline at end of file