diff --git a/blog-admin/src/routes/comments.ts b/blog-admin/src/routes/comments.ts index 10bc7ccf..2d623cd4 100644 --- a/blog-admin/src/routes/comments.ts +++ b/blog-admin/src/routes/comments.ts @@ -429,10 +429,11 @@ export async function createComment(ctx: Ctx): Promise { 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 { parentAuthor, siteName, siteUrl, - pageTitle: page.title || pageTitle, + // 标题优先用本次请求带上来的(前端 artalk.html 传 .Title,总是最新、 + // 干净);pages.title 是「首次评论时建页」落库的存量值,可能是历史 + // 脏数据(例:/comment.html 曾存成字面量 "留言",邮件就渲染成 + // 《"留言"》)。存量仅在本次没带标题时兜底。 + pageTitle: pageTitle || page.title, pageUrl: pageAccessibleUrl(pageKey, siteUrl), }); } catch (e) { diff --git a/themes/Ying/assets/css/main.css b/themes/Ying/assets/css/main.css index af49ddf6..812d4f3e 100644 --- a/themes/Ying/assets/css/main.css +++ b/themes/Ying/assets/css/main.css @@ -4309,17 +4309,6 @@ input[type=submit] { background: rgba(255, 255, 255, 0.12); } -/* CSS 平滑滚动:只在程序触发的滚动期间由 JS 挂到 上,滚完即摘。 - 滚动交给浏览器合成器处理,不占 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; diff --git a/themes/Ying/assets/js/modules/floating-tools.js b/themes/Ying/assets/js/modules/floating-tools.js index e6e4ba2a..416982e0 100644 --- a/themes/Ying/assets/js/modules/floating-tools.js +++ b/themes/Ying/assets/js/modules/floating-tools.js @@ -1,40 +1,37 @@ -// 平滑滚动 —— 纯 CSS 实现(scroll-behavior: smooth)。 -// 之前用 rAF + window.scrollTo 自绘动画:每一帧都在主线程上强制重排重绘, -// 移动端长页面(尤其带 backdrop-filter / 大图的)掉帧明显,非常卡。 -// CSS 平滑滚动由浏览器合成器接管,不占 JS 主线程,移动端丝滑得多。 -// 做法:只在滚动期间临时给 挂 .css-smooth-scroll 类,滚完就摘 —— -// 避免全局常开平滑滚动干扰 PJAX 切页回顶、锚点跳转这些场景。 +// 平滑滚动 —— 逻辑 1:1 对齐 UIkit 的 scrollIntoView(rAF + 余弦缓动 + 按距离算时长)。 +// 踩坑记录:之前试过「滚动期间给 挂 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() {