diff --git a/app/helpers.php b/app/helpers.php index 96617b9..375bfd7 100644 --- a/app/helpers.php +++ b/app/helpers.php @@ -431,18 +431,36 @@ function fb_entry_json(array $ev, string $detail = '', string $edit = '', array $evOwn = ($evKind === 'custom' && $evRef > 0); $evW = !empty($ev['_can_write']); // 由调用方按日历权限给出(见模板) + /* 🔴🔴 v93(用户 2026-10-25):「编辑重复日历时,也会有这三个选项 + (修改当前 / 修改从现在到以后 / 修改全部)」 + —— 浮层里的编辑表单要能给出这三档,就必须知道**打开的是哪一次**: + `calendar/event` 路由从 `?d=YYYY-MM-DD` 读它(`index.php` 里的 `$occ`), + `templates/calendar/event.php` 再据此输出那三档单选。 + 而浮层的 `edit` 地址原来是 `url('calendar/event', ['id'=>…, 'embed'=>1])` —— + **没带 d** ⇒ 表单里只显示"改全部",另外两档连出现都不出现, + 用户会以为"重复日程不能只改这一次"(实测就是这样)。 + ⚠️ 只在 `source='event'`(自定义日程)且 occ 是合法日期时追加: + 观影 / 旅程 / 任务 / 纪念日这些派生条目没有"第几次"的概念。 + ⚠️ 是否加 `&` 按 `$edit` 里有没有 `?` 判 —— `url()` 生成的一定带, + 但别赌(换一处调用方就会静默拼出 `?id=1&embed=1d=2026-10-25` 这种废地址)。 */ + $editUrl = $edit; + $occDay = (string)($ev['occ'] ?? ''); + if ($evOwn && $editUrl !== '' && preg_match('/^\d{4}-\d{2}-\d{2}$/', $occDay)) { + $editUrl .= (strpos($editUrl, '?') === false ? '?' : '&') . 'd=' . rawurlencode($occDay); + } + $payload = [ 'title' => trim((string)($ev['title'] ?? '')), 'rows' => $rows, 'detail' => $detail, - 'edit' => $edit, + 'edit' => $editUrl, // 新增(v49) 'source' => $evOwn ? 'event' : 'derived', 'ref' => $evOwn ? $evRef : 0, // 日程 id(前端删/改要按它定位,别从 URL 里抠) 'del' => ($evOwn && $evW) ? url('calendar/event_del') : '', 'info' => $evOwn ? url('calendar/event', ['id' => $evRef, 'embed' => 'detail']) : '', 'repeat' => (string)($ev['repeat'] ?? '') !== '', - 'occ' => (string)($ev['occ'] ?? ''), + 'occ' => $occDay, /* 🔴 v78(用户 2026-10-22):**就地面板**的地址。 首页「近期任务」点开时不再只给一行行只读信息,而是直接给出这张 面板(状态 / 看板 / 子任务 / 附件 / 评论 都能就地改),用户不必离开首页。 diff --git a/assets/css/app.css b/assets/css/app.css index d64ceb8..cdb3bd3 100644 --- a/assets/css/app.css +++ b/assets/css/app.css @@ -5415,7 +5415,21 @@ button.btn, a.btn, summary.btn, .seg-btn, .icon-btn, .rv-act-btn, .cs-btn, .bf-b ③ **加上下边距**,让标题与正文之间出现明确的段落呼吸。 ⚠️ 为什么从 h4 起而不是 h1/h2:见 `Markdown::render()` 里的说明 —— h1/h2 留给页面本身,笔记里的标题不许盖过页面标题。 - 所以这里必须把 **h4/h5/h6 这三级**都调大,光调 h4 不够。 */ + 所以这里必须把 **h4/h5/h6 这三级**都调大,光调 h4 不够。 + + 🔴🔴 v93(2026-10-25,全量套件抓出来的**上一版没做完的地方**): + 上一版只把 h4(`##`)做到 17px,**h5(`###`)留在 15.5px、h6 留在 14.5px** + —— 15.5 只比正文 15 大 0.5px,肉眼仍是"和正文一样";h6 更是**比正文还小**。 + 而上面这段注释自己写的是"三级都要调大、要拉开 ≥2px",实现违背了自己的规则。 + ⇒ 阶梯整体重排,**最小的一级也必须 ≥ 正文 + 2px**: + h3 `#` → 22px + h4 `##` → 20px + h5 `###` → 18px + h6 `####`起(Markdown 里 h4 及更深都收敛到 h6)→ 17px,另配 --muted 灰 + 四级字号**两两不同**(A9d 守的就是"同级同号、异级不同号"), + 且 h6 靠灰色与字号双重区分 —— 否则 `####` 与正文挤在一起。 + ⚠️ 正文基准是 `.note-read-block { font-size: 15px }`(见下面那条规则)。 + 将来正文若改号,这里的四级要**一起**复核,别只改 h4。 */ .md-h { margin: 16px 0 8px; font-weight: 600; @@ -5423,10 +5437,10 @@ button.btn, a.btn, summary.btn, .seg-btn, .icon-btn, .rv-act-btn, .cs-btn, .bf-b line-height: 1.45; } .md-h:first-child { margin-top: 0; } -h3.md-h { font-size: 18px; } -h4.md-h { font-size: 17px; } -h5.md-h { font-size: 15.5px; } -h6.md-h { font-size: 14.5px; color: var(--muted); } +h3.md-h { font-size: 22px; } +h4.md-h { font-size: 20px; } +h5.md-h { font-size: 18px; } +h6.md-h { font-size: 17px; color: var(--muted); } .md-list { margin: 4px 0 8px; padding-left: 22px; } .md-li { margin: 2px 0; } /* 多级清单:子层用空心点,层级一眼可分(用户第 12 条"清单可以多级") */ diff --git a/assets/js/cs_picker.js b/assets/js/cs_picker.js new file mode 100644 index 0000000..0abd69a --- /dev/null +++ b/assets/js/cs_picker.js @@ -0,0 +1,163 @@ +/* ============================================================================ + 通用「色点下拉」(`.cs`)—— 全站一份 + ---------------------------------------------------------------------------- + 起源 v44(用户 2026-10-12 第 1 条):「新建日程,放在哪本,改成日历本后面加冒号。 + 现在这句话和日历本下拉框还有前面的颜色,仍然不在一行,我建议把颜色放在下拉框里面, + 就跟电影的影院选择一样」。当时那段交互是**内联在 templates/calendar/index.php** + 里的,只服务日程页那一个 `#calPicker`。 + + 🔴🔴 v93(用户 2026-10-25): + 「如果新建日程,日历本是拉不下来的,是空的,好像只有一个"工作"什么的。」 + 真根因:首页「今天的日程」卡片右上角的「新建日程」,用的是**与日程页同一份** + 模板块(`templates/calendar/_add_modal.php`),HTML 一模一样 ⇒ 日历本那颗 + 按钮和菜单都在页面上;可**交互全在日程页的内联脚本里**,首页没有那一段 + ⇒ 按钮点了没反应、菜单永远 `hidden`。用户看到的就是"拉不下来"; + 而按钮上那个默认值(「工作」)是服务端渲染出来的,所以"只有一个工作"。 + + ⇒ 把那段交互抽成这一份,**按标记初始化**(`[data-cs-picker]`): + 不认具体 id、不认哪个页面 —— 谁 require 了模板块谁就能用。 + + 契约(模板只需给容器加 `data-cs-picker`,其余全按类名找): +
+
' + + (hasOcc ? '' + : '

' + + '这条是从列表打开的、不知道是哪一次,只能整条删除。

'); } if (panelEl()) { panelEl().classList.remove('is-wide'); } setTip(''); diff --git a/templates/_entry_popup.php b/templates/_entry_popup.php index 3b0afa9..a393d25 100644 --- a/templates/_entry_popup.php +++ b/templates/_entry_popup.php @@ -17,14 +17,27 @@ * (观影→那一天的观影详情页;旅程→那个行程所在的旅程页)。 * **这种情况下,下面就没有编辑、也没有删除,就只有一个详情。**」 * - * ⇒ 底栏的三态(由 JS 切,见 assets/js/entry_popup.js): - * ① **详情态**(点条目进来):`详情`(蓝) + `编辑`(黄,仅自定义日程且可写) + `删除`(红,同前) - * —— 三颗一律**靠右**(用户:"都放在下面的右侧")。 - * 派生条目(观影/旅程/任务/纪念日/农历/节假日)只有 `详情` 一颗。 - * ② **展开态**(点「详情」,仅自定义日程):面板变宽、正文换成服务端的完整详情。 + * ⇒ 底栏的状态(由 JS 切,见 assets/js/entry_popup.js): + * ① **详情态**(点条目进来): + * · 自定义日程(source='event'):`编辑`(黄) + `删除`(红) + * · 派生条目(观影/旅程/任务/纪念日/农历/节假日):只有 `详情`(蓝) 一颗 + * —— 按钮一律**靠右**(用户:"都放在下面的右侧")。 + * ② **展开态**(`?embed=detail` 的只读块):v93 起**没有入口**了(函数保留兜底)。 * ③ **编辑态**:正文换成编辑表单;底栏 = `取消` + `保存`。 * 删除范围(重复日程)另有一种临时态,见 JS。 * + * 🔴🔴 v93(用户 2026-10-25)——**用户原话**: + * 「如果点击日程,首页上点击一个日程后会有一个详情。点完之后还是一个弹出框, + * 这个弹出框跟原来的弹出框几乎一模一样。这样的话,这个详情就没有意义了, + * 所以弹出来就只显示两个按钮吧:一个『编辑』,一个『删除』。 + * 要注意,删除的时候需要有几个选项:删除当前;删除从现在到以后;删除全部。 + * 编辑重复日历时,也会有这三个选项。」 + * ⇒ **自定义日程不再有「详情」那颗蓝按钮**:浮层正文本来就是它的全部信息, + * 再点一次只是把同一份信息换个更宽的框再画一遍。 + * ⚠️ 派生条目的「详情」**必须保留**(v49 用户明确要求:"就只有一个详情")。 + * ⚠️ 那颗按钮的显隐**全由 JS 按来源决定**,服务端只给数据(这里一律 `hidden`, + * 避免"先闪一下三颗、随即收起一颗")。 + * * ⚠️ 空壳放在 **layout.php** 里(全站一份):三处入口在不同页面, * 放各页面迟早有一个忘了 include —— 那种"某些页面点了没反应"最难查。 * ⚠️ 面板里**不放任何服务端内容**(除了那颗 CSRF):同一页一百条日程也只有一个浮层,开销可忽略。 @@ -47,7 +60,9 @@
diff --git a/templates/calendar/_add_modal.php b/templates/calendar/_add_modal.php index 4a507ea..46e3dd0 100644 --- a/templates/calendar/_add_modal.php +++ b/templates/calendar/_add_modal.php @@ -156,7 +156,20 @@ $fbAddModalAttr = ($calBack === 'home');
日历本 -
+ +
diff --git a/templates/calendar/index.php b/templates/calendar/index.php index 9861427..c1e596f 100644 --- a/templates/calendar/index.php +++ b/templates/calendar/index.php @@ -600,99 +600,19 @@ $headText = $view === 'month' ? date('Y 年 n 月', $ts) syncDot(); } - /* ============================================================ - 🔴 v44(用户 2026-10-12 第 1 条):日历本自绘下拉(色点画进下拉里) - ------------------------------------------------------------ - 与观影的影院下拉同一套交互,但**状态写在选中的原生 ``, - 所以这里不需要模板另给一份数据、也不会两边不同步。 */ - function build() { - list.innerHTML = ''; - Array.prototype.forEach.call(sel.options, function (o) { - var b = document.createElement('button'); - b.type = 'button'; - b.className = 'cs-item' + (o.disabled ? ' is-disabled' : ''); - b.setAttribute('role', 'option'); - if (o.disabled) { b.disabled = true; b.title = o.getAttribute('title') || ''; } - b.setAttribute('data-id', o.value); - b.setAttribute('data-color', o.getAttribute('data-color') || ''); - b.setAttribute('data-text', o.textContent || ''); - var d = document.createElement('span'); - d.className = 'cs-dot'; - d.style.background = o.getAttribute('data-color') || ''; - var s = document.createElement('span'); - s.textContent = o.textContent || ''; - b.appendChild(d); b.appendChild(s); - list.appendChild(b); - }); - } - function paint() { - var o = sel.options[sel.selectedIndex]; - if (!o) { return; } - text.textContent = o.textContent || ''; - var c = o.getAttribute('data-color') || ''; - if (c) { dot.hidden = false; dot.style.background = c; } else { dot.hidden = true; } - } - function closeMenu() { - menu.hidden = true; - btn.setAttribute('aria-expanded', 'false'); - picker.setAttribute('data-open', '0'); - } - function openMenu() { - menu.hidden = false; - btn.setAttribute('aria-expanded', 'true'); - picker.setAttribute('data-open', '1'); - } - - build(); - paint(); - /* ⚠️ 原生 select 的 change 会由别处(旧 syncDot、无 JS 路径)触发, - 这里也监听一次,保证显示与真值永远一致。 */ - sel.addEventListener('change', paint); - - btn.addEventListener('click', function (e) { - e.stopPropagation(); - if (menu.hidden) { openMenu(); } else { closeMenu(); } - }); - menu.addEventListener('click', function (e) { - var it = e.target.closest ? e.target.closest('.cs-item') : null; - if (!it || it.disabled) { return; } - e.stopPropagation(); - sel.value = it.getAttribute('data-id'); - sel.dispatchEvent(new Event('change', { bubbles: true })); - closeMenu(); - }); - /* 点空白处收起(用 document 监听 + 判定 inside,不额外造遮罩节点) - ⚠️ 必须在**捕获阶段**判定:菜单项的 click 会 stopPropagation, - 但那是冒泡阶段 —— 捕获阶段先于它,这里判"点在不在 picker 里" - 就够精确了(点在菜单里 → 不收)。 */ - document.addEventListener('click', function (e) { - if (menu.hidden) { return; } - if (picker.contains(e.target)) { return; } - closeMenu(); - }, true); - document.addEventListener('keydown', function (e) { - if (e.key === 'Escape' && !menu.hidden) { closeMenu(); } - }); - })(); + ⚠️ 这里只留一句兜底调用。正常情况下通用脚本在 DOM ready 时已经扫过 + (那时元素在、只是被 `hidden` 藏着,不影响绑定),这里什么都绑不上 —— + 它是幂等的,绑过一次的容器自带 `data-cs-ready`,不会被绑第二遍。 + ⚠️ **不要**把交互搬回这里"顺便兼容一下":搬回来就等于又只有本页能用。 */ + if (typeof window.fbInitCsPicker === 'function') { window.fbInitCsPicker(card || document); } var form = document.getElementById('calAddForm'); var tip = document.getElementById('calAddTip'); diff --git a/templates/layout.php b/templates/layout.php index fdfbe91..017cb73 100644 --- a/templates/layout.php +++ b/templates/layout.php @@ -265,6 +265,15 @@ require __DIR__ . '/_entry_popup.php'; ?> ⚠️ 与上面 `task_add.js` 并存不冲突:那一份仍管 `#taskAddCard`(v78 已上线), 这一份只认 `[data-fb-add-modal]`,两者选择器不相交。 */ ?> + +