feat(comments): 人机验证改点按式 + 分割线可配置 + 投票契约修复
- 人机验证抽成 human.js 模块,改成 Cloudflare Turnstile 风格的「点一下验证」, 控件移到「评论一下」发送按钮左侧(不再用图形验证码那一套) - 留言页 / 文章页两段内联脚本(友链弹窗、人机验证)抽成模块随 page-only.js 加载。 原先内联 <script> 写在 #pjax-container 外,PJAX 只换容器内容不重新执行它, 从首页 PJAX 进留言页会出现「申请友链卡片点了没反应」「没有人机验证控件」 - 友链申请弹窗 #flApplyBox 隐藏滚动条(内容仍可滚,避开细滚动条 + 毛玻璃的割裂感) - 内容分割线抽成 partial divider.html,hugo.toml [params.divider] 可切 icon(原生 uk-divider-icon)/ image(本地图片),当前用 icon - 投票契约修复(后端 blog-admin):Artalk 客户端把方向编在 target_name 后缀 (comment_up / comment_down / page_up / page_down)、路径只有两段, 而原先只注册了三段 :choice 路由 → 点赞/踩一路 404、界面点了没反应。 补 POST /votes/:target_name/:target_id,新增 parseVoteTarget() 同时兼容两套契约 (名字里与路径里方向矛盾时判非法,不静默取其一),错误文案改用实体名 - 无身份点赞不再静默失败:artalk.js 加 __atkVoteGuard, 未填昵称/邮箱时拦截并提示「填写昵称和邮箱后才能点赞」, 同时把光标送到昵称框(多个同名输入框逐个试,以 focus 真生效为判据)
This commit is contained in:
1 parent
50a906e07c
commit
e0c5ac5739
12 files changed
+513
-274
No files matched your search
@@ -168,7 +168,7 @@
|
||||
'.atk-svc-down .atk-main-editor{opacity:.55!important;filter:grayscale(1)!important}' +
|
||||
'.atk-svc-down .atk-copyright{display:none!important}' +
|
||||
'.atk-svc-down .atk-main-editor *{pointer-events:none}' +
|
||||
'.atk-svc-down .atk-human-box{display:none}' +
|
||||
'.atk-svc-down .atk-human-tf{display:none}' +
|
||||
'.atk-svc-down .atk-list-body{min-height:0!important}' +
|
||||
'.atk-editor-off-bar{display:flex;align-items:center;gap:8px;margin:12px 0 10px;padding:9px 14px;border-radius:8px;' +
|
||||
'background:rgba(7,193,96,.06);border:1px solid rgba(7,193,96,.22);font-size:13px;line-height:1.6;color:var(--text-main,#333)}' +
|
||||
@@ -1384,3 +1384,74 @@ artalk.on('list-loaded', function() {
|
||||
listSkeletonHide();
|
||||
}
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// 投票前的身份提示 —— 别让读者点了「赞同」却毫无反应
|
||||
//
|
||||
// 服务端(artalk-cf)的投票语义和 Artalk 原生一致:**票必须挂在身份上**,
|
||||
// 未登录时要求 body 带 name + email,否则返回 400。而客户端只在
|
||||
// `userInfo.name && userInfo.email` 都齐了才会带上身份(见 Artalk.js 的
|
||||
// getUserFields)——没填过的读者点下去就是一个静默失败:
|
||||
// 请求打到服务端、报错、界面上的数字纹丝不动,读者只会觉得「点坏了」。
|
||||
//
|
||||
// 这里在 capture 阶段把这类点击拦下来(拦在 Artalk 自己的 handler 之前),
|
||||
// 给一句人话提示并把光标送到昵称输入框。填过身份的直接放行,不干扰。
|
||||
// ============================================================================
|
||||
(function () {
|
||||
if (window.__atkVoteGuard) return; // PJAX 会重复执行本文件,只装一次
|
||||
window.__atkVoteGuard = true;
|
||||
|
||||
var USER_KEY = 'ArtalkUser'; // Artalk 的用户存储键(nick / email / link / token)
|
||||
|
||||
function readUser() {
|
||||
try {
|
||||
var raw = localStorage.getItem(USER_KEY);
|
||||
return raw ? JSON.parse(raw) : null;
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// 已登录(有 token)服务端认 Authorization;游客必须昵称 + 邮箱
|
||||
function hasIdentity(u) {
|
||||
if (!u) return false;
|
||||
if (u.token) return true;
|
||||
return !!(u.nick && u.email);
|
||||
}
|
||||
|
||||
document.addEventListener('click', function (e) {
|
||||
var el = e.target;
|
||||
var btn = el && el.closest ? el.closest('.atk-common-action-btn') : null;
|
||||
if (!btn) return;
|
||||
if (!/赞同|反对|顶|踩/.test(btn.textContent || '')) return;
|
||||
if (hasIdentity(readUser())) return;
|
||||
|
||||
// 拦在 Artalk 之前:不发那个注定 400 的请求,也不让数字假装动一下
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
if (window.showToast) window.showToast('填写昵称和邮箱后才能点赞', 'warning');
|
||||
else if (window.Toast && window.Toast.warning) window.Toast.warning('填写昵称和邮箱后才能点赞');
|
||||
|
||||
// 把光标送到昵称输入框。
|
||||
// ⚠️ 不能只 querySelector 第一个:Artalk 的 DOM 里存在**不止一个** nick 输入框
|
||||
// (隐藏模板、未激活的回复框也算),第一个匹配到的往往不可焦点,
|
||||
// 对它 focus() 会被浏览器静默忽略 —— activeElement 一点不变,很容易误判成"生效了"。
|
||||
// 所以逐个候选试,并以「focus 真的落到它身上」为唯一判据。
|
||||
function focusFirstUsable(sel) {
|
||||
var list = document.querySelectorAll(sel);
|
||||
for (var i = 0; i < list.length; i++) {
|
||||
var el = list[i];
|
||||
if (!(el.offsetWidth || el.offsetHeight || el.getClientRects().length)) continue;
|
||||
try { el.focus({ preventScroll: true }); } catch (err) { try { el.focus(); } catch (e2) { continue; } }
|
||||
if (document.activeElement === el) return el;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
var nick = focusFirstUsable('.atk-header [name="nick"]') || focusFirstUsable('[name="nick"]');
|
||||
if (nick) {
|
||||
try { nick.scrollIntoView({ block: 'center', behavior: 'smooth' }); } catch (err) { /* 老浏览器忽略 */ }
|
||||
}
|
||||
}, true);
|
||||
})();
|
||||
Reference in new issue
Block a user