0、问题描述
引入 PJAX / InstantClick 等异步加载的脚本后,切换页面时出现以下异常状况:
- 无法点击“回复”按钮;
- 评论失败,返回(刷新页面)后重新评论正常。
问题 2 的问题在于 Typecho 的“检查评论来源页 URL 是否与文章链接一致”和“反垃圾保护”和主题、PJAX、InstantClick 的设置不一定兼容,问题 1 的问题是由于 Typecho 在<head>
中输出TypechoComment
的内容,而 PJAX / InstantClick 一般只重载<body>
。
1、解决方法
1.1、问题1
首先在 Typecho 后台的 设置-评论-评论提交关闭“检查评论来源页 URL 是否与文章链接一致”和“反垃圾保护”。
我知道关闭之后会被机器人刷垃圾评论,因此我推荐使用评论验证码或评论反垃圾插件,例如我之前用了很久的 reCAPTCHAv3 插件。
最近我发现,由于 ReCaptcha 镜像站出了点问题,不是“无法连接到 ReCaptcha” 就是“你被判定为了机器人”,几乎无法使用,于是换成了 Cloudflare Turnstile,申请、配置参考本站文章:
(还没写)
接下来兼容 PJAX / InstantClick:
如果使用 PJAX,编辑/var/Widget/Security.php
第 50 行,改为:
1 | public function getTokenUrl($path) { $parts = parse_url($path); $params = array(); if (!empty($parts['query'])) { parse_str($parts['query'], $params); } $params['_'] = $this->getToken(preg_replace('/\?_pjax=%23body/', '', $this->request->getRequestUrl())); $parts['query'] = http_build_query($params); return Typecho_Common::buildUrl($parts); } |
然后将 PJAX / InstantClick 的刷新函数(页面更改后执行的函数)中加入:
1 | var now_url_comment = location.href + ' '; history.replaceState(null,'',now_url_comment.replace('/comment ','')); |
即可解决问题。
1.2、问题2
解决 JavaScript 加载问题(回复按钮的问题),有两种思路:
- 更新页面时重新加载函数;
- 直接删除 Typecho 自带的评论函数,修改为适配自己模板的函数。
1.2.1、思路1
第一种方法更通用,只需在输出评论时添加:
1 | <script>respondId = '<?php $this->respondId(); ?>';</script> |
如果模板的此处没有$this
(例如我的),可以用 JS 获得。例如评论表单是:
1 | <div id="<?php $this->respondId(); ?>" class="response-box"> ... </div> |
可以将上述输出代码改为:
1 | <script>respondId = document.getElementsByClassName("response-box")[0].id;</script> |
无论哪种方法,输出后页面中 responseId
将加载到 JS 的变量中,因此只需在 PJAX / InstantClick 的刷新函数中加入:
1 | window.TypechoComment = { dom : function (id) { return document.getElementById(id); }, create : function (tag, attr) { var el = document.createElement(tag); for (var key in attr) { el.setAttribute(key, attr[key]); } return el; }, reply : function (cid, coid) { var comment = this.dom(cid), parent = comment.parentNode, response = this.dom(respondId()), input = this.dom('comment-parent'), form = 'form' == response.tagName ? response : response.getElementsByTagName('form')[0], textarea = response.getElementsByTagName('textarea')[0]; if (null == input) { input = this.create('input', { 'type' : 'hidden', 'name' : 'parent', 'id' : 'comment-parent' }); form.appendChild(input); } input.setAttribute('value', coid); if (null == this.dom('comment-form-place-holder')) { var holder = this.create('div', { 'id' : 'comment-form-place-holder' }); response.parentNode.insertBefore(holder, response); } comment.appendChild(response); this.dom('cancel-comment-reply-link').style.display = ''; if (null != textarea && 'text' == textarea.name) { textarea.focus(); } return false; }, cancelReply : function () { var response = this.dom(respondId()), holder = this.dom('comment-form-place-holder'), input = this.dom('comment-parent'); if (null != input) { input.parentNode.removeChild(input); } if (null == holder) { return true; } this.dom('cancel-comment-reply-link').style.display = 'none'; holder.parentNode.insertBefore(response, holder); return false; } }; |
如果仍不生效,可以根据自己的模板内容稍作修改。
1.2.2、思路2
我不太喜欢 Typecho 回复评论时,把评论框 append
到了要回复的主题下面,因此我直接将自带的评论函数删去,即把<head>
部分的:
1 | $this->header(''); |
改为:
1 | $this->header('commentReply='); |
当然,我推荐把头部一大堆乱七八糟的也去掉:
1 | $this->header('generator=&template=&pingback=&xmlrpc=&wlw=&atom=&rss1=&rss2=&commentReply='); |
然后在评论输出代码中,加入 JS:
1 | <script type="text/javascript"> (function () { window.TypechoComment = { dom : function (id) { return document.getElementById(id); }, create : function (tag, attr) { var el = document.createElement(tag); for (var key in attr) { el.setAttribute(key, attr[key]); } return el; }, reply : function (cid, coid) { var comment = this.dom(cid), parent = comment.parentNode, response = this.dom('<?php echo $this->respondId(); ?>'), input = this.dom('comment-parent'), form = 'form' == response.tagName ? response : response.getElementsByTagName('form')[0], textarea = response.getElementsByTagName('textarea')[0]; if (null == input) { input = this.create('input', { 'type' : 'hidden', 'name' : 'parent', 'id' : 'comment-parent' }); form.appendChild(input); } input.setAttribute('value', coid); if (null == this.dom('comment-form-place-holder')) { var holder = this.create('div', { 'id' : 'comment-form-place-holder' }); response.parentNode.insertBefore(holder, response); } comment.appendChild(response); this.dom('cancel-comment-reply-link').style.display = ''; if (null != textarea && 'text' == textarea.name) { textarea.focus(); } return false; }, cancelReply : function () { var response = this.dom('<?php echo $this->respondId(); ?>'), holder = this.dom('comment-form-place-holder'), input = this.dom('comment-parent'); if (null != input) { input.parentNode.removeChild(input); } if (null == holder) { return true; } this.dom('cancel-comment-reply-link').style.display = 'none'; holder.parentNode.insertBefore(response, holder); return false; } }; })(); </script> |
如果不需要上述的评论框移动,只需删去comment.appendChild(response);
即可。
这里使用了$this->responseId
,如果没有这个值同样可以用 JS 动态获取:
1 | <script type="text/javascript"> (function () { responseId = document.getElementsByClassName("response-box")[0].id; window.TypechoComment = { dom : function (id) { return document.getElementById(id); }, create : function (tag, attr) { var el = document.createElement(tag); for (var key in attr) { el.setAttribute(key, attr[key]); } return el; }, reply : function (cid, coid) { var comment = this.dom(cid), parent = comment.parentNode, response = this.dom('<?php echo $this->respondId(); ?>'), input = this.dom('comment-parent'), form = 'form' == response.tagName ? response : response.getElementsByTagName('form')[0], textarea = response.getElementsByTagName('textarea')[0]; if (null == input) { input = this.create('input', { 'type' : 'hidden', 'name' : 'parent', 'id' : 'comment-parent' }); form.appendChild(input); } input.setAttribute('value', coid); if (null == this.dom('comment-form-place-holder')) { var holder = this.create('div', { 'id' : 'comment-form-place-holder' }); response.parentNode.insertBefore(holder, response); } comment.appendChild(response); this.dom('cancel-comment-reply-link').style.display = ''; if (null != textarea && 'text' == textarea.name) { textarea.focus(); } return false; }, cancelReply : function () { var response = this.dom('<?php echo $this->respondId(); ?>'), holder = this.dom('comment-form-place-holder'), input = this.dom('comment-parent'); if (null != input) { input.parentNode.removeChild(input); } if (null == holder) { return true; } this.dom('cancel-comment-reply-link').style.display = 'none'; holder.parentNode.insertBefore(response, holder); return false; } }; })(); </script> |
经过测试发现,即使删去了comment.appendChild(response);
在取消评论时仍然会把表单内的一个<div>
移动到莫名其妙的地方,很不巧 Cloudflare Turnstile 验证码会受此影响直接消失。解决方法可以是在cancelReply
时重载验证码,当然最好的方法是直接彻底去掉移动评论框这个功能:
1 | <script defer> (function () { responseId = document.getElementsByClassName("respond")[0].id; window.TypechoComment = { dom : function (id) { return document.getElementById(id); }, create : function (tag, attr) { var el = document.createElement(tag); for (var key in attr) { el.setAttribute(key, attr[key]); } return el; }, reply : function (cid, coid) { var comment = this.dom(cid), parent = comment.parentNode, response = this.dom(responseId), input = this.dom('comment-parent'), form = 'form' == response.tagName ? response : response.getElementsByTagName('form')[0], textarea = response.getElementsByTagName('textarea')[0]; if (null == input) { input = this.create('input', { 'type' : 'hidden', 'name' : 'parent', 'id' : 'comment-parent' }); form.appendChild(input); } input.setAttribute('value', coid); this.dom('cancel-comment-reply-link').style.display = ''; if (null != textarea && 'text' == textarea.name) { textarea.focus(); } return false; }, cancelReply : function () { var response = this.dom(responseId), input = this.dom('comment-parent'); if (null != input) { input.parentNode.removeChild(input); } this.dom('cancel-comment-reply-link').style.display = 'none'; return false; } }; })(); </script> |
至此完美解决~
解决 Typecho 使用 PJAX/InstantClick 导致的评论问题
评论