fix(comments): 悄悄话允许管理员发表 + 邮件标题优先用实时标题;返回顶部改 UIkit 同款动画

- comments.ts: 去掉旧匿名语义遗留的 !admin 前置条件 —— 博主(登录态)发的悄悄话
  以前被忽略标志、入库 is_anonymous=0 变成公开评论
- 邮件标题改用本次请求带来的 page_title(前端 .Title 总是最新),D1 pages.title
  仅兜底:存量脏数据 /comment.html 曾存成字面量 "留言",邮件渲染出《"留言"》
  (线上已清洗 17 行带引号标题 + 3 篇被污染成「留言」的文章页标题)
- 返回顶部:CSS 挂类+scrollTo 同帧执行会被浏览器按旧计算样式瞬移("一下子就上去"),
  改 UIkit scrollIntoView 同款 rAF + 余弦 ease-in-out + 40*dist^0.375 时长(上限 1.2s),
  滚轮/触摸打断、reduced-motion 瞬移;main.css 移除 .css-smooth-scroll
This commit is contained in:
zqlit committed 2026-10-05 10:52:13 +08:00
1 parent 4fa592c11c
commit f7e0596218
3 files changed
+34 -43

No files matched your search

+9 -4
View File
@@ -429,10 +429,11 @@ export async function createComment(ctx: Ctx): Promise<Response> {
return fail(403, 'Admin access required', { need_login: true });
}
// 匿名评论:真实身份只留在 users 表(回复通知照发、自动填充不受污染),
// 对外一律显示「匿名」。管理员身份本来就不是匿名的,忽略标志。
// 悄悄话评论:只藏内容、身份照常显示,管理员同样可以发(2026-10-05 踩坑:
// 这里以前是旧「匿名藏身份」语义,带 !admin 把管理员的悄悄话标志整个忽略,
// 导致博主发的悄悄话入库 is_anonymous=0、内容对公众可见)
const anonymous =
!admin && (body.anonymous === true || body.anonymous === 1 || body.anonymous === '1' || body.anonymous === 'true');
body.anonymous === true || body.anonymous === 1 || body.anonymous === '1' || body.anonymous === 'true';
// 匿名用户:按 name+email 找或建
let author = user;
@@ -517,7 +518,11 @@ export async function createComment(ctx: Ctx): Promise<Response> {
parentAuthor,
siteName,
siteUrl,
pageTitle: page.title || pageTitle,
// 标题优先用本次请求带上来的(前端 artalk.html 传 .Title,总是最新、
// 干净);pages.title 是「首次评论时建页」落库的存量值,可能是历史
// 脏数据(例:/comment.html 曾存成字面量 "留言",邮件就渲染成
// 《"留言"》)。存量仅在本次没带标题时兜底。
pageTitle: pageTitle || page.title,
pageUrl: pageAccessibleUrl(pageKey, siteUrl),
});
} catch (e) {
-11
View File
@@ -4309,17 +4309,6 @@ input[type=submit] {
background: rgba(255, 255, 255, 0.12);
}
/* CSS 平滑滚动:只在程序触发的滚动期间由 JS 挂到 <html> 上,滚完即摘。
滚动交给浏览器合成器处理,不占 JS 主线程 —— 移动端不再像 rAF 自绘那样掉帧。 */
html.css-smooth-scroll {
scroll-behavior: smooth;
}
@media (prefers-reduced-motion: reduce) {
html.css-smooth-scroll {
scroll-behavior: auto;
}
}
#back-to-top {
opacity: 0;
visibility: hidden;
+25 -28
View File
@@ -1,40 +1,37 @@
// 平滑滚动 —— 纯 CSS 实现(scroll-behavior: smooth)。
// 之前用 rAF + window.scrollTo 自绘动画:每一帧都在主线程上强制重排重绘,
// 移动端长页面(尤其带 backdrop-filter / 大图的)掉帧明显,非常卡。
// CSS 平滑滚动由浏览器合成器接管,不占 JS 主线程,移动端丝滑得多。
// 做法:只在滚动期间临时给 <html> 挂 .css-smooth-scroll 类,滚完就摘 ——
// 避免全局常开平滑滚动干扰 PJAX 切页回顶、锚点跳转这些场景。
// 平滑滚动 —— 逻辑 1:1 对齐 UIkit 的 scrollIntoView(rAF + 余弦缓动 + 按距离算时长)。
// 踩坑记录:之前试过「滚动期间给 <html> 挂 scroll-behavior:smooth 类再 scrollTo」的
// 纯 CSS 方案,类和 scrollTo 在同一帧执行,浏览器可能拿旧的计算样式直接瞬移;
// UIkit 的做法也印证了这点 —— 它在 JS 动画期间反而强制 scroll-behavior:auto,
// 避免 CSS 平滑滚动和 JS 动画互相打架。rAF 每帧只是改一次 scrollTop,主线程开销
// 极小;时长按距离动态计算(UIkit 同款 40 * dist^0.375),短距离不拖沓、长距离不瞬移。
window.smoothScrollTo = function(targetY) {
targetY = Math.max(0, Math.floor(targetY || 0));
if (Math.abs(window.scrollY - targetY) < 2) return;
var root = document.documentElement;
// 上一次滚动还没收尾就直接再来一次:先摘干净旧状态
if (window.__smoothScrollCleanup) window.__smoothScrollCleanup();
if (window.__smoothScrollRAF) cancelAnimationFrame(window.__smoothScrollRAF);
var reduced = false;
try { reduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches; } catch (e) {}
if (reduced) { window.scrollTo(0, targetY); return; } // 用户要求减少动态效果:瞬移
root.classList.add('css-smooth-scroll');
window.scrollTo(0, targetY); // 类在位,浏览器自动走平滑滚动
var done = function () {
if (window.__smoothScrollTimer) { clearTimeout(window.__smoothScrollTimer); window.__smoothScrollTimer = null; }
window.removeEventListener('scrollend', done);
window.removeEventListener('wheel', done, { passive: true } );
window.removeEventListener('touchstart', done, { passive: true });
root.classList.remove('css-smooth-scroll');
window.__smoothScrollCleanup = null;
var startY = window.scrollY;
var dist = targetY - startY;
// UIkit 同款时长:100px≈240ms、1000px≈530ms、5000px≈1.1s,上限 1.2s 兜底
var duration = Math.min(1200, 40 * Math.pow(Math.abs(dist), 0.375));
var t0 = null, stopped = false;
var stop = function () { stopped = true; }; // 用户滚轮/触摸接管,立即让位
window.addEventListener('wheel', stop, { once: true, passive: true });
window.addEventListener('touchstart', stop, { once: true, passive: true });
// UIkit 同款缓动:余弦式 ease-in-out,缓起缓收、中段最快
var ease = function (k) { return 0.5 * (1 - Math.cos(Math.PI * k)); };
var step = function (ts) {
if (stopped) return;
if (t0 === null) t0 = ts;
var p = Math.min(1, (ts - t0) / duration);
window.scrollTo(0, startY + dist * ease(p));
if (p < 1) window.__smoothScrollRAF = requestAnimationFrame(step);
else window.__smoothScrollRAF = null;
};
window.__smoothScrollCleanup = done;
// scrollend(Chrome 114+/FF 109+)在滚动真正停下的瞬间触发;
// 不支持的浏览器用超时兜底摘类。滚轮/触摸会打断 CSS 平滑滚动,
// 提前收尾把类摘掉,不影响用户接着手动滚。
window.addEventListener('scrollend', done, { once: true });
window.addEventListener('wheel', done, { once: true, passive: true });
window.addEventListener('touchstart', done, { once: true, passive: true });
window.__smoothScrollTimer = setTimeout(done, 1000);
window.__smoothScrollRAF = requestAnimationFrame(step);
};
window.refreshFloatingTools = function() {