fix(ui): 提交防重复覆盖验证码路径、waline 按页加载、懒加载、safe-area、硬编码文案接字典
1) 防重复点击原来漏了最主要的一条路径
footer 的全局 submit 监听会禁用按钮,但**程序化 form.submit() 不触发 submit 事件** ——
走 reCAPTCHA / Turnstile 的流程是在回调里调 form.submit() 的,而默认配置
captcha_type = recaptcha,也就是主提问流一直没有防重复。登录、注册、找回密码、
投票、注销账号同样走这个回调。
→ 把「切提交中状态」抽成 tbBeginSubmit(form, trigger) 挂到 window,
验证码回调先调它再 submit。7 个 data-callback="onSubmit" 的模板全部受益。
顺带加了 data-tb-restore 的重复覆盖保护,避免 bfcache 复位后按钮卡在转圈。
2) waline.js / waline.css 从全站改按页加载
只有 home.html 与 item.html 用评论区,但此前挂在 header 里 —— 登录、后台、投票、
私聊每页都下载解析 waline.js(2.15 未压缩约 200KB)。实测 /login 已无引用。
3) 图片懒加载:explore 列表头像与题目配图加 loading="lazy"(首屏推荐位不加,避免影响 LCP)。
4) 移动端底栏:body 只预留固定 56px,而底栏 padding 含 safe-area-inset-bottom。
带 Home Indicator 的 iPhone 上 inset 约 34px、底栏实际约 88px,最后约 32px
(页脚备案、存活计时)被压在栏下,滚到底也看不到。改成 calc(56px + env(...))。
5) 硬编码中文接字典(en/ja 用户此前直接看到中文):
history-template 的「查看回答 / 加载更多 / 加载中... / 无更多留言」、
单题页「@xxx 以前回答过的问题」(字典里本来就有 ask.history)、
投票页「(最多 N 项)」改为带参数的整句(原先前半走字典、后半手写中文)。
新增 4 个 key × 4 语言。
实测:箱主语言设为 en 后箱主页显示 View answer ×2 / Load more / No more posts,中文零残留。
This commit is contained in:
+44
-24
@@ -62,6 +62,49 @@
|
||||
});
|
||||
}
|
||||
|
||||
// tbBeginSubmit 把表单切到「提交中」状态:触发按钮换成 spinner + 本地化文案,
|
||||
// 并禁用表单内所有按钮。
|
||||
//
|
||||
// 抽成具名函数(而不是内联在下面的事件监听里)是因为 **程序化 submit() 不触发
|
||||
// submit 事件**:走 reCAPTCHA / Turnstile 的流程是在回调里调 form.submit() 的,
|
||||
// 原本完全绕过下面那个监听器 —— 而默认部署 captcha_type = recaptcha,
|
||||
// 也就是主提问流一直没有防重复点击保护。那些回调现在先调本函数再 submit()。
|
||||
function tbBeginSubmit(form, trigger) {
|
||||
if (!form) return;
|
||||
|
||||
// 下载 / 新窗口类表单(target="_blank",如导出):当前页不发生导航,也没有 bfcache
|
||||
// 复位机会,进入“提交中”状态后按钮会永远卡在转圈 + 禁用。这类表单直接跳过提交反馈。
|
||||
if (form.getAttribute('target') === '_blank') return;
|
||||
|
||||
// 触发提交的按钮(programmatic submit 时调用方传 null,回退到首个 submit 按钮)
|
||||
var btn = trigger || form.querySelector('button[type="submit"]');
|
||||
|
||||
// 表单含已选中的文件 → 提示“上传中”,否则“提交中”
|
||||
var hasFile = false;
|
||||
form.querySelectorAll('input[type="file"]').forEach(function(f) {
|
||||
if (f.files && f.files.length > 0) hasFile = true;
|
||||
});
|
||||
var label = hasFile ? SUBMIT_I18N.uploading : SUBMIT_I18N.submitting;
|
||||
|
||||
if (btn && btn.tagName === 'BUTTON') {
|
||||
// 已经切过一次就不再覆盖 data-tb-restore,否则会把 spinner 的 HTML 当成原始内容,
|
||||
// 导致 bfcache 复位后按钮永远停在转圈上。
|
||||
if (!btn.hasAttribute('data-tb-restore')) {
|
||||
btn.setAttribute('data-tb-restore', btn.innerHTML);
|
||||
}
|
||||
btn.innerHTML = '<span uk-spinner="ratio: 0.6"></span> ' + label;
|
||||
}
|
||||
|
||||
// 禁用表单内所有按钮,防止重复提交
|
||||
form.querySelectorAll('button').forEach(function(b) {
|
||||
b.disabled = true;
|
||||
b.setAttribute('data-tb-submitting', '1');
|
||||
});
|
||||
}
|
||||
// 挂到 window 上供 header 里的验证码回调调用(脚本顺序:header → footer,
|
||||
// 但回调发生在用户点击之后,此时本函数已定义)
|
||||
window.tbBeginSubmit = tbBeginSubmit;
|
||||
|
||||
document.addEventListener('submit', function(e) {
|
||||
var form = e.target;
|
||||
if (!form || form.tagName !== 'FORM') return;
|
||||
@@ -73,30 +116,7 @@
|
||||
return;
|
||||
}
|
||||
|
||||
// 下载 / 新窗口类表单(target="_blank",如导出):当前页不发生导航,也没有 bfcache
|
||||
// 复位机会,进入“提交中”状态后按钮会永远卡在转圈 + 禁用。这类表单直接跳过提交反馈。
|
||||
if (form.getAttribute('target') === '_blank') return;
|
||||
|
||||
// 触发提交的按钮(programmatic submit 时 e.submitter 为 null,回退到首个 submit 按钮)
|
||||
var trigger = e.submitter || form.querySelector('button[type="submit"]');
|
||||
|
||||
// 表单含已选中的文件 → 提示“上传中”,否则“提交中”
|
||||
var hasFile = false;
|
||||
form.querySelectorAll('input[type="file"]').forEach(function(f) {
|
||||
if (f.files && f.files.length > 0) hasFile = true;
|
||||
});
|
||||
var label = hasFile ? SUBMIT_I18N.uploading : SUBMIT_I18N.submitting;
|
||||
|
||||
if (trigger && trigger.tagName === 'BUTTON') {
|
||||
trigger.setAttribute('data-tb-restore', trigger.innerHTML);
|
||||
trigger.innerHTML = '<span uk-spinner="ratio: 0.6"></span> ' + label;
|
||||
}
|
||||
|
||||
// 禁用表单内所有按钮,防止重复提交
|
||||
form.querySelectorAll('button').forEach(function(b) {
|
||||
b.disabled = true;
|
||||
b.setAttribute('data-tb-submitting', '1');
|
||||
});
|
||||
tbBeginSubmit(form, e.submitter);
|
||||
});
|
||||
|
||||
// bfcache 还原时复位按钮
|
||||
|
||||
@@ -24,11 +24,9 @@
|
||||
<link rel="stylesheet" href="/static/css/base.css?v={{ .StaticVersion }}"/>
|
||||
{{ if .ThemeSlug }}<!-- 箱主主题:只覆盖配色/图案/页面背景,不涉及布局;仅提问箱相关页面加载 -->
|
||||
<link rel="stylesheet" href="/static/css/themes/{{ .ThemeSlug }}.css?v={{ .StaticVersion }}"/>{{ end }}
|
||||
<!-- Waline 评论区样式:非阻塞加载,不阻塞首屏渲染 -->
|
||||
<link rel="stylesheet" href="https://r2.337845818.best/tamabox/cdn/waline/client/v2/waline-no-commits.css" media="print" onload="this.media='all'"/>
|
||||
<script defer src="https://cdnjs.snrat.com/ajax/libs/uikit/3.3.3/js/uikit.min.js"></script>
|
||||
<script defer src="https://cdnjs.snrat.com/ajax/libs/uikit/3.3.3/js/uikit-icons.min.js"></script>
|
||||
<!-- dayjs/alpinejs/vconsole/waline.js 已迁 cdnjs.snrat.com(sha256 与官方一致,国内实测快 2-7x) -->
|
||||
<!-- dayjs/alpinejs/vconsole 已迁 cdnjs.snrat.com(sha256 与官方一致,国内实测快 2-7x) -->
|
||||
<script defer src="https://cdnjs.snrat.com/ajax/libs/dayjs/1.10.8/dayjs.min.js"></script>
|
||||
<script defer src="https://cdnjs.snrat.com/ajax/libs/alpinejs/3.9.0/cdn.min.js"></script>
|
||||
{{ if .VConsole }}
|
||||
@@ -36,7 +34,6 @@
|
||||
<script>new window.VConsole()</script>
|
||||
{{ end }}
|
||||
<!-- jQuery 已改为按需加载:仅 easy.qrcode 二维码页(list/question-not-found/profile)自行引入 -->
|
||||
<script defer src="https://cdnjs.snrat.com/ajax/libs/waline/2.15.8/waline.js"></script>
|
||||
{{ if eq .CaptchaType "math" }}
|
||||
<!-- 算数验证码:弹窗模式,不影响原布局 -->
|
||||
<style>
|
||||
@@ -105,7 +102,21 @@
|
||||
}</script>
|
||||
{{ else }}
|
||||
<script src="{{.RecaptchaDomain}}/recaptcha/api.js" async defer></script>
|
||||
<script>function onSubmit() {document.getElementById('form').submit();}</script>
|
||||
<script>
|
||||
// reCAPTCHA v3 回调:拿到 token 后手动提交表单。
|
||||
//
|
||||
// 程序化 submit() **不触发 submit 事件**,所以 footer 里那个全局监听器
|
||||
// (禁用按钮 + 转圈)对它完全无效 —— 而默认配置 captcha_type = recaptcha 时,
|
||||
// 这就是主提问流的提交路径,等于一直没有防重复点击。
|
||||
// 这里先显式切到「提交中」再 submit。tbBeginSubmit 定义在 footer.html,
|
||||
// 脚本顺序在本文件之后;回调发生在用户点击之后,那时它已就绪,仍做一次判空兜底。
|
||||
function onSubmit() {
|
||||
var f = document.getElementById('form');
|
||||
if (!f) return;
|
||||
if (window.tbBeginSubmit) { window.tbBeginSubmit(f, null); }
|
||||
f.submit();
|
||||
}
|
||||
</script>
|
||||
{{ end }}
|
||||
{{ if ne SentryDSN "" }}
|
||||
<script
|
||||
|
||||
Reference in New Issue
Block a user