diff --git a/internal/i18n/dict_en.go b/internal/i18n/dict_en.go index 7fa7d3d..8732e3e 100644 --- a/internal/i18n/dict_en.go +++ b/internal/i18n/dict_en.go @@ -299,6 +299,9 @@ var enDict = map[string]string{ "qlist.unpinShort": "Unpin", "qlist.myVotes": "My votes", "qlist.loadMore": "Load more", + "qlist.noMore": "No more posts", + "qlist.loading": "Loading...", + "qlist.viewAnswer": "View answer", "qlist.newVote": "New vote", "qlist.edit": "Edit", "qlist.untitledVote": "Untitled vote", @@ -630,6 +633,7 @@ var enDict = map[string]string{ "vote.page.rankHint": "Rank each option 1-N (smaller = higher, no duplicates)", "vote.page.rankPlaceholder": "Rank", "vote.page.multipleHint": "Multiple choice", + "vote.page.multipleHintWithMax": "Multiple choice (up to %d)", "vote.page.customOption": "Custom option (optional)", "vote.page.customPlaceholder": "Type your own option", "vote.page.customOther": "Other", diff --git a/internal/i18n/dict_ja.go b/internal/i18n/dict_ja.go index 1feb0a0..86a08e0 100644 --- a/internal/i18n/dict_ja.go +++ b/internal/i18n/dict_ja.go @@ -299,6 +299,9 @@ var jaDict = map[string]string{ "qlist.unpinShort": "解除", "qlist.myVotes": "作成した投票", "qlist.loadMore": "もっと読み込む", + "qlist.noMore": "これ以上の投稿はありません", + "qlist.loading": "読み込み中...", + "qlist.viewAnswer": "回答を見る", "qlist.newVote": "投票を作成", "qlist.edit": "編集", "qlist.untitledVote": "無題の投票", @@ -629,6 +632,7 @@ var jaDict = map[string]string{ "vote.page.rankHint": "各選択肢に 1-N の順位を入力してください(小さいほど上位、重複不可)", "vote.page.rankPlaceholder": "順位", "vote.page.multipleHint": "複数選択可", + "vote.page.multipleHintWithMax": "複数選択可(最大 %d 件)", "vote.page.customOption": "カスタム選択肢(任意)", "vote.page.customPlaceholder": "あなたの選択肢を入力", "vote.page.customOther": "その他", diff --git a/internal/i18n/dict_zh_cn.go b/internal/i18n/dict_zh_cn.go index e104215..4539a23 100644 --- a/internal/i18n/dict_zh_cn.go +++ b/internal/i18n/dict_zh_cn.go @@ -299,6 +299,9 @@ var zhCNDict = map[string]string{ "qlist.unpinShort": "取消置顶", "qlist.myVotes": "我创建的投票", "qlist.loadMore": "加载更多", + "qlist.noMore": "无更多留言", + "qlist.loading": "加载中...", + "qlist.viewAnswer": "查看回答", "qlist.newVote": "新建投票", "qlist.edit": "编辑", "qlist.untitledVote": "未命名投票", @@ -630,6 +633,7 @@ var zhCNDict = map[string]string{ "vote.page.rankHint": "请为每个选项填写 1-N 的排名(数字越小越靠前,不可重复)", "vote.page.rankPlaceholder": "排名", "vote.page.multipleHint": "可多选", + "vote.page.multipleHintWithMax": "可多选(最多 %d 项)", "vote.page.customOption": "补充选项(可选)", "vote.page.customPlaceholder": "填写你心中的其他选项", "vote.page.customOther": "其他", diff --git a/internal/i18n/dict_zh_tw.go b/internal/i18n/dict_zh_tw.go index 0249d1d..7dd4449 100644 --- a/internal/i18n/dict_zh_tw.go +++ b/internal/i18n/dict_zh_tw.go @@ -299,6 +299,9 @@ var zhTWDict = map[string]string{ "qlist.unpinShort": "取消置頂", "qlist.myVotes": "我建立的投票", "qlist.loadMore": "載入更多", + "qlist.noMore": "無更多留言", + "qlist.loading": "載入中...", + "qlist.viewAnswer": "查看回答", "qlist.newVote": "新建投票", "qlist.edit": "編輯", "qlist.untitledVote": "未命名投票", @@ -629,6 +632,7 @@ var zhTWDict = map[string]string{ "vote.page.rankHint": "請為每個選項填寫 1-N 的排名(數字越小越靠前,不可重複)", "vote.page.rankPlaceholder": "排名", "vote.page.multipleHint": "可多選", + "vote.page.multipleHintWithMax": "可多選(最多 %d 項)", "vote.page.customOption": "補充選項(可選)", "vote.page.customPlaceholder": "填寫你心中的其他選項", "vote.page.customOther": "其他", diff --git a/static/css/base.css b/static/css/base.css index 22e1e6e..f65bd10 100644 --- a/static/css/base.css +++ b/static/css/base.css @@ -854,6 +854,10 @@ a.tb-chip:hover { } /* 给页面底部预留空间,避免页脚内容被固定底栏遮挡 */ body { - padding-bottom: 56px; + /* 必须加上 safe-area-inset-bottom:底栏自身的 padding 已经包含了它, + 而 body 只预留了固定的 56px。带 Home Indicator 的 iPhone 上 inset 约 34px, + 底栏实际高度约 88px —— 最后约 32px 内容(页脚备案、存活计时)会被压在栏下, + 滚到底也看不到。 */ + padding-bottom: calc(56px + env(safe-area-inset-bottom)); } } diff --git a/templates/base/footer.html b/templates/base/footer.html index efa894d..65e4309 100644 --- a/templates/base/footer.html +++ b/templates/base/footer.html @@ -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 = ' ' + 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 = ' ' + label; - } - - // 禁用表单内所有按钮,防止重复提交 - form.querySelectorAll('button').forEach(function(b) { - b.disabled = true; - b.setAttribute('data-tb-submitting', '1'); - }); + tbBeginSubmit(form, e.submitter); }); // bfcache 还原时复位按钮 diff --git a/templates/base/header.html b/templates/base/header.html index 2db6fe8..06ca306 100644 --- a/templates/base/header.html +++ b/templates/base/header.html @@ -24,11 +24,9 @@ {{ if .ThemeSlug }} {{ end }} - - - + {{ if .VConsole }} @@ -36,7 +34,6 @@ {{ end }} - {{ if eq .CaptchaType "math" }}