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:
1 parent
4fa592c11c
commit
f7e0596218
3 files changed
+34
-43
No files matched your search
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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() {
|
||||
|
||||
Reference in new issue
Block a user