/** * comment-anonymous —— 「匿名评论」开关 * * 场景:有人想说话但不想暴露身份(昵称/头像/网址都能对上号)。 * 勾选后提交的评论对外显示为「匿名」:头像换通用图、网址/徽标/等级/IP归属 * 全部隐藏。真实昵称和邮箱仍存在后端 users 表里 —— 有人回复时通知照发、 * 下次来「填邮箱自动带出昵称」不受污染。博主后台能看到真实身份(审核需要)。 * * 实现要点: * - 开关插在 textarea 和底栏之间**独立一行**(右对齐小开关),不挤底栏 —— * 移动端底栏本来就塞了表情/图片/发送按钮,再塞一个就挤爆了。 * - Artalk 是 editorTravel 单实例游走,全站只有一个活的 .atk-main-editor, * fetch 拦截读它的开关状态即可。 * - 借已有的 fetch 拦截链给 POST /api/v2/comments 的 body 注入 anonymous:true。 */ (function () { if (window.__atkAnonReady) return; // PJAX 重复执行防抖 window.__atkAnonReady = true; var TIP = '对外显示为「匿名」,头像和网址会隐藏;邮箱只用于接收回复通知,对外不可见'; // 取「当前活着的」编辑器里的开关:优先可见的(editorTravel 游走的那个) function currentToggle() { var eds = document.querySelectorAll('.atk-main-editor'); var fallback = null; for (var i = 0; i < eds.length; i++) { var cb = eds[i].querySelector('.atk-anon-toggle input'); if (!cb) continue; if (eds[i].offsetParent !== null) return cb; // 可见编辑器优先 if (!fallback) fallback = cb; } return fallback; } function inject(editor) { if (!editor || !editor.querySelector) return; if (editor.querySelector('.atk-anon-toggle')) return; // 已注入 var bottom = editor.querySelector('.atk-bottom'); if (!bottom) return; // 结构没就绪,等下轮扫描 var label = document.createElement('label'); label.className = 'atk-anon-toggle'; label.title = TIP; var cb = document.createElement('input'); cb.type = 'checkbox'; var txt = document.createElement('span'); txt.textContent = '匿名评论'; label.appendChild(cb); label.appendChild(txt); bottom.parentNode.insertBefore(label, bottom); } var scanTimer = null; function scheduleScan() { if (scanTimer) return; scanTimer = setTimeout(function () { scanTimer = null; try { var eds = document.querySelectorAll('.atk-main-editor'); for (var i = 0; i < eds.length; i++) inject(eds[i]); } catch (e) {} }, 120); } try { // 编辑器由 Artalk 动态创建(PJAX / 点回复时游走重建位置),观察兜底 new MutationObserver(scheduleScan).observe(document.body, { childList: true, subtree: true }); } catch (e) {} // MutationObserver 不监听「隐藏节点变可见」,用点击/聚焦兜底扫一遍 document.addEventListener('click', scheduleScan, true); document.addEventListener('focusin', scheduleScan, true); scheduleScan(); // ── fetch 拦截:给新建评论请求注入 anonymous 标志 ────────────────────── // Artalk 的提交走 fetch + JSON body;链上已有别的拦截器(提交反馈/失败 // 翻译),这里只改 init.body,然后原样放行,互不干扰。 var origFetch = window.fetch; if (typeof origFetch === 'function') { window.fetch = function (input, init) { try { var url = String(typeof input === 'string' ? input : (input && input.url) || ''); var method = String((init && init.method) || (input && input.method) || 'GET').toUpperCase(); if (method === 'POST' && /\/api\/v2\/comments(\?|$)/.test(url) && init && typeof init.body === 'string') { var cb = currentToggle(); if (cb && cb.checked) { var data = JSON.parse(init.body); if (data && typeof data === 'object' && !data.anonymous) { data.anonymous = true; init = Object.assign({}, init, { body: JSON.stringify(data) }); } } } } catch (e) {} // 解析失败就按原样发,绝不能因为匿名开关挂掉评论 return origFetch.apply(this, [input, init]); }; } })();