From 437cd339ad58f6648ea7a7dc685be28a33cae579 Mon Sep 17 00:00:00 2001 From: zqlit Date: Sun, 4 Oct 2026 18:30:30 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E7=AE=A1=E7=90=86=E5=91=98=E7=BC=96?= =?UTF-8?q?=E8=BE=91=E8=B7=B3=E8=BF=87=E9=82=AE=E7=AE=B1=E6=A0=A1=E9=AA=8C?= =?UTF-8?q?=20+=20=E8=BF=94=E5=9B=9E=E9=A1=B6=E9=83=A8=20rAF=20=E4=B8=9D?= =?UTF-8?q?=E6=BB=91=E6=BB=9A=E5=8A=A8=20+=20admin.js=20=E6=AD=A3=E5=88=99?= =?UTF-8?q?=E7=AC=94=E8=AF=AF=20+=20=E9=A1=B5=E9=9D=A2=E6=A0=87=E9=A2=98?= =?UTF-8?q?=E6=B1=A1=E6=9F=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - artalk.js: beforeSubmit 在编辑态跳过昵称/邮箱校验(管理员改评论不再被拦) - floating-tools.js: 返回顶部/去评论改 rAF + easeInOutCubic 自绘,原生 smooth 不可靠 - admin.js: 修 5 处 //s //n 正则笔误(整脚本 SyntaxError 致后台全灰),需 wrangler deploy - artalk.html: 显式传 pageTitle(不再读 document.title,PJAX 竞态致文章页标题被记成「留言」) - artalk.js: conf 缓存合并前剔除 pageKey/pageTitle/pageThumb,杜绝跨页串值 --- blog-admin/public/admin/admin.js | 10 +++---- themes/Ying/assets/js/modules/artalk.js | 24 +++++++++++++-- .../Ying/assets/js/modules/floating-tools.js | 30 +++++++++++++++++-- themes/Ying/layouts/partials/artalk.html | 5 ++++ 4 files changed, 59 insertions(+), 10 deletions(-) diff --git a/blog-admin/public/admin/admin.js b/blog-admin/public/admin/admin.js index 67890bdc..f3103619 100644 --- a/blog-admin/public/admin/admin.js +++ b/blog-admin/public/admin/admin.js @@ -714,7 +714,7 @@ async function viewSettings() { if (/ip_region:[\s\S]*?enabled:\s*(true|false)/.test(v)) { v = v.replace(/(ip_region:[\s\S]*?enabled:\s*)(true|false)/, '$1' + on); } else { - v += (v.endsWith('\n') ? '' : '\n') + 'ip_region://n enabled: ' + on + '\n'; + v += (v.endsWith('\n') ? '' : '\n') + 'ip_region:\n enabled: ' + on + '\n'; } t.value = v; toast('已切换为 ' + (on ? '显示' : '隐藏') + ',保存后生效'); @@ -722,7 +722,7 @@ async function viewSettings() { // 人机验证快捷开关(自研「一键验证」):读写 yaml 里的 human_check.enabled, // 保存时由服务端同步到 KV(评论提交时读 KV,不占 D1 额度) - var hcOn = /human_check:[\s\S]*?enabled://s*true/.test(original); + var hcOn = /human_check:[\s\S]*?enabled:\s*true/.test(original); var hcRow = document.createElement('div'); hcRow.style.cssText = 'display:flex;align-items:center;gap:8px;margin-bottom:10px;padding:10px 12px;' + 'background:var(--surface);border:1px solid var(--line);border-radius:6px'; @@ -736,10 +736,10 @@ async function viewSettings() { var on = this.checked; var t = $('#yamlTxt'); var v = t.value; - if (/human_check:[\s\S]*?enabled://s*(true|false)/.test(v)) { - v = v.replace(/(human_check:[\s\S]*?enabled://s*)(true|false)/, '$1' + on); + if (/human_check:[\s\S]*?enabled:\s*(true|false)/.test(v)) { + v = v.replace(/(human_check:[\s\S]*?enabled:\s*)(true|false)/, '$1' + on); } else { - v += (v.endsWith('\n') ? '' : '\n') + 'human_check://n enabled: ' + on + '\n'; + v += (v.endsWith('\n') ? '' : '\n') + 'human_check:\n enabled: ' + on + '\n'; } t.value = v; toast('人机验证已' + (on ? '开启' : '关闭') + ',保存后生效'); diff --git a/themes/Ying/assets/js/modules/artalk.js b/themes/Ying/assets/js/modules/artalk.js index 1a1ba0e5..271621eb 100644 --- a/themes/Ying/assets/js/modules/artalk.js +++ b/themes/Ying/assets/js/modules/artalk.js @@ -771,7 +771,19 @@ window.initArtalk = function() { // Try to read cache try { var raw = localStorage.getItem(confKey); - if (raw) cachedConf = JSON.parse(raw); + if (raw) { + cachedConf = JSON.parse(raw); + // 剔除页面级字段:缓存来自「上一个页面」的实例 conf,若把它的 + // pageKey/pageTitle 合并进当前页就会串页 —— 历史上文章页在后台 + // 标题被污染成「留言」(用户从留言页 PJAX 进文章页)就是这个原因。 + // pageKey / pageTitle 现已全部由 window.artalkConfig 显式提供, + // 这里删掉属于双保险,防止 config 哪天漏传又被旧值钻空子。 + if (cachedConf && typeof cachedConf === 'object') { + ['pageKey', 'pageTitle', 'pageThumb'].forEach(function(k) { + delete cachedConf[k]; + }); + } + } } catch(e) { console.warn('Failed to parse Artalk cache:', e); } @@ -1596,9 +1608,15 @@ artalk.on('list-loaded', function() { editor = ed || editor; var content = ''; try { content = String(ed.getContentFinal() || '').trim(); } catch (e) {} - var els = headerEls(); - if (!content) { toast('warning', '评论内容不能为空'); return; } + + // 编辑已有评论(管理员)用登录身份提交,没有昵称/邮箱输入框, + // 硬校验会把管理员挡在「编辑」外面 —— 直接放行,内容校验已在上面做过 + var st = ''; + try { st = (ed && typeof ed.getState === 'function' && ed.getState()) || ''; } catch (e) {} + if (st === 'edit') { next(); return; } + + var els = headerEls(); var nick = val(els.nick), email = val(els.email); if (!nick) { toast('warning', '请先填写昵称'); focusEl(els.nick); return; } if (!email) { toast('warning', '请先填写邮箱(有人回复你时才能通知到)'); focusEl(els.email); return; } diff --git a/themes/Ying/assets/js/modules/floating-tools.js b/themes/Ying/assets/js/modules/floating-tools.js index 23bf77a8..24594f57 100644 --- a/themes/Ying/assets/js/modules/floating-tools.js +++ b/themes/Ying/assets/js/modules/floating-tools.js @@ -1,3 +1,29 @@ +// 自绘平滑滚动:浏览器原生 behavior:'smooth' 又快又硬(约 300ms 且不可调), +// 还会被系统的「减少动态效果」降级成瞬移。用 rAF + easeInOutCubic 自绘, +// 时长随距离自适应,滚动一旦被用户滚轮/触摸打断立即让位。 +window.smoothScrollTo = function(targetY, duration) { + var startY = window.scrollY; + var diff = targetY - startY; + if (Math.abs(diff) < 2) return; + // 短距离快、长距离慢一些,但都 clamp 在 420~950ms,保证观感一致 + var dur = duration || Math.min(950, Math.max(420, Math.abs(diff) * 0.38)); + if (window.__smoothScrollRAF) cancelAnimationFrame(window.__smoothScrollRAF); + var t0 = null, canceled = false; + var cancel = function() { canceled = true; }; + window.addEventListener('wheel', cancel, { once: true, passive: true }); + window.addEventListener('touchstart', cancel, { once: true, passive: true }); + var ease = function(t) { return t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2; }; + var step = function(ts) { + if (canceled) return; + if (t0 === null) t0 = ts; + var p = Math.min(1, (ts - t0) / dur); + window.scrollTo(0, startY + diff * ease(p)); + if (p < 1) window.__smoothScrollRAF = requestAnimationFrame(step); + else window.__smoothScrollRAF = null; + }; + window.__smoothScrollRAF = requestAnimationFrame(step); +}; + window.refreshFloatingTools = function() { if (typeof window.initFloatingTools !== 'function') return; var delays = [0, 60, 180]; @@ -105,7 +131,7 @@ window.initFloatingTools = function() { backToTopButton.addEventListener('click', function(e) { e.preventDefault(); - window.scrollTo({ top: 0, behavior: 'smooth' }); + window.smoothScrollTo(0); }); } @@ -206,7 +232,7 @@ window.initFloatingTools = function() { var comments = document.getElementById('Comments') || document.querySelector('.artalk'); if (comments) { var y = comments.getBoundingClientRect().top + window.scrollY - 20; - window.scrollTo({ top: y, behavior: 'smooth' }); + window.smoothScrollTo(y); } }); } diff --git a/themes/Ying/layouts/partials/artalk.html b/themes/Ying/layouts/partials/artalk.html index 62f53f82..e0d20866 100644 --- a/themes/Ying/layouts/partials/artalk.html +++ b/themes/Ying/layouts/partials/artalk.html @@ -253,6 +253,11 @@ server: '{{ .Site.Params.artalk.server }}', site: '{{ .Site.Params.artalk.site }}', pageKey: '{{ .RelPermalink }}', + {{/* pageTitle 必须显式传:Artalk 不传时会读 document.title,而 PJAX 切页 + 早期脚本执行时 document.title 还是上一个页面的(经典竞态),历史上 + 数据库里一堆文章页的标题被污染成「留言」就是这么来的。head.html 的 + 就是纯 .Title,这里与它保持一致。jsonify 防标题里有引号炸模板。 */}} + pageTitle: {{ .Title | jsonify }}, emoticons: '{{ .Site.Params.artalk.emoticons | absURL }}', aiComment: null };