/** * comment-autofill —— 老朋友「填邮箱自动带出昵称和网址」 * * 场景:很多人隔几个月来评论一次,早忘了上次填的昵称/网址,每次都重新想。 * 这个模块做两件事: * 1. 访客开始在昵称框打字时,弹一次 toast 提示「填邮箱可以自动带回来」; * 2. 邮箱填完(失焦或格式完整)就问后端:这个邮箱上次评论用的什么昵称/网址, * 拿到后回填到空着的输入框,让老朋友少打几个字。 * * 隐私红线:后端 /comments/lookup **只回昵称和网址,永远不回邮箱**; * 邮箱只作为查询参数发给评论后端(跟提交评论本身一样),不在本地落任何存储, * 也不打日志。查不到就安静地什么都不做,绝不给访客「你在被查询」的感觉。 * * 实现说明:用 document 级事件委托(capture),不绑定具体输入框 —— * Artalk 的编辑器会反复重建(PJAX 切页、点回复、登录态变化),委托一劳永逸。 */ (function () { if (window.__atkAutofillReady) return; // PJAX 重复执行防抖 window.__atkAutofillReady = true; var HINT_KEY = 'atk_friend_hint'; // sessionStorage:提示只弹一次 var asked = {}; // 查过的邮箱 → 结果,避免重复请求 var timer = null; function toast(msg, type, duration) { try { if (window.Toast && typeof window.Toast[type || 'info'] === 'function') { window.Toast[type || 'info'](msg, duration); } } catch (e) {} } function isEmail(s) { return /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/.test(String(s || '').trim()); } function editorOf(el) { // 线上 Artalk 的编辑器容器是 .atk-main-editor(旧版/别处可能是 .atk-editor), // 兜底再往上找整个 .artalk 根 —— 宁可范围大一点,也别让委托匹配不到 return (el && el.closest) ? el.closest('.atk-main-editor, .atk-editor, .artalk, .atk-layer-wrap') : null; } function fieldIn(editor, name) { return editor ? editor.querySelector('input[name="' + name + '"]') : null; } /** Artalk 对受控输入有自己的状态,改完必须补一个 input 事件它才认 */ function setVal(input, value) { if (!input) return; input.value = value; try { input.dispatchEvent(new Event('input', { bubbles: true })); } catch (e) {} } function showHint(editor) { try { if (sessionStorage.getItem(HINT_KEY)) return; sessionStorage.setItem(HINT_KEY, '1'); } catch (e) { return; } // 存储被禁用就别弹了,宁缺毋滥 var email = fieldIn(editor, 'email'); if (email && email.value.trim()) return; // 已经填了邮箱,不用提示 toast('老朋友?填一下邮箱,昵称和网址会自动帮你带回来 ✨', 'info', 4500); } function fillFrom(editor, data) { if (!data || !data.found) return; var nick = fieldIn(editor, 'nick'); var link = fieldIn(editor, 'link'); var name = String(data.name || '').trim(); var url = String(data.link || '').trim(); if (!name) return; var filled = []; // 昵称:空着,或者只打了历史昵称的开头(说明 TA 正在凭印象敲同一个名字) // 才替 TA 补全 —— 已经敲了一个完全不同的名字就别抢,那是故意的。 if (nick) { var cur = nick.value.trim(); var lower = cur.toLowerCase(); var lowerName = name.toLowerCase(); if (!cur || lowerName.indexOf(lower) === 0) { if (cur !== name) { setVal(nick, name); filled.push('昵称'); } } } if (link && !link.value.trim() && url) { setVal(link, url); filled.push('网址'); } if (filled.length) { toast('欢迎回来,' + name + '!已帮你填好' + filled.join('和') + ' 👋', 'success', 4000); } } function lookup(editor, email) { var key = email.toLowerCase(); if (asked[key]) { fillFrom(editor, asked[key]); return; } var server = (window.artalkConfig && window.artalkConfig.server || '').replace(/\/+$/, ''); if (!server) return; // 超时兜底:评论后端在国内访问偶尔抖,别让访客干等 var ctrl = typeof AbortController !== 'undefined' ? new AbortController() : null; var killer = null; if (ctrl) killer = setTimeout(function () { try { ctrl.abort(); } catch (e) {} }, 8000); fetch(server + '/api/v2/comments/lookup?email=' + encodeURIComponent(email), { method: 'GET', credentials: 'omit', signal: ctrl ? ctrl.signal : undefined }).then(function (r) { return r.ok ? r.json() : null; }).then(function (d) { asked[key] = d || { found: false }; fillFrom(editor, asked[key]); }).catch(function () { asked[key] = { found: false }; // 失败也记一下,别反复打同一个请求 }).finally(function () { if (killer) clearTimeout(killer); }); } // ── 事件委托(capture:Artalk 内部可能 stopPropagation)────────────── document.addEventListener('input', function (e) { var el = e.target; if (!el || el.tagName !== 'INPUT' || !el.name) return; var editor = editorOf(el); if (!editor) return; // 只管 Artalk 的评论框 if (el.name === 'nick') { if (el.value.trim()) showHint(editor); // 打了第一个字就开始提示 return; } if (el.name === 'email') { var v = el.value.trim(); if (isEmail(v)) { if (timer) clearTimeout(timer); timer = setTimeout(function () { lookup(editor, v); }, 700); // 停手再查 } } }, true); document.addEventListener('focusout', function (e) { var el = e.target; if (!el || el.tagName !== 'INPUT' || el.name !== 'email') return; var editor = editorOf(el); if (!editor) return; var v = el.value.trim(); if (!isEmail(v)) return; if (timer) clearTimeout(timer); lookup(editor, v); // 离开邮箱框就立刻查 }, true); })();