终于把AJAX评论搞好了QwQ,效果可以在这篇文章下面评论试试(所以在本页可以随意划水2333333)。
2020.2.16 UPD:过了这么久我总算有了一点长进QAQ,现在已经基本明白了这种方法的原理。由于此方法兼容性比较好(比如不影响评论过滤插件)而且写起来更方便,所以在主题重构的时候改用了这种方式。
其实只要对form
表单提交和ajax
有个大致的了解,上面这篇文章看起来就通俗易懂了QAQ。
这两个功能与PJAX以及AJAX评论有冲突,需要关闭(应该是有办法支持的,不过这两个功能其实没啥用,所以关了也没事)。
可以在functions.php
的themeInit
函数中加上两句话:
function themeInit($archive){
Helper::options()->commentsAntiSpam=false; //关闭评论反垃圾
Helper::options()->commentsCheckReferer=false; //关闭检查评论来源页URL是否与文章链接一致
}
这样就不用手动设置了。
首先需要阻止原先的提交,改用AJAX来提交评论。
//#comment-form是提交评论的form
$('#comment-form').submit(function(event){
var commentdata=$(this).serializeArray();
//serializeArray()可以获取到表单数据
$.ajax({
url:$(this).attr('action'), //表单的提交链接
type:$(this).attr('method'), //表单的提交方式(post)
data:commentdata, //需要提交的数据
beforeSend:function(){}, //AJAX提交前
error:function(){}, //AJAX提交失败
success:function(data){} //AJAX提交成功
});
return false; //阻止原先提交
});
如果AJAX提交成功,在success
中会返回一个data
,它是提交成功之后跳转到的页面的HTML代码。
提交失败分为两种:1.AJAX提交失败(比如你没网了🙃)。2.AJAX提交成功,但是被typecho拒绝(比如评论为空)。
第二种情况下,data
将会返回类似这样的内容:
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Error</title>
<style></style>
</head>
<body>
<div class="container">
必须填写评论内容
</div>
</body>
</html>
比较有标志性的内容是<title>Error</title>
,我们可以通过检测这个字符串是否存在来区分评论提交成功或失败。然后用正则表达式获取到<div class="container"></div>
中的错误信息就行了。
var error=/<title>Error<\/title>/;
if (error.test(data)){ //如果存在<title>Error</title>
var text=data.match(/<div(.*?)>(.*?)<\/div>/is);
//text[0]是匹配结果,text[1]是第一个(.*?),text[2]是第二个(.*?)
var str='发生了未知错误';if (text!=null) str=text[2];
mdui.alert(str,'评论失败'); //mdui的提示框
}
如果在3中没有出现<title>Error</title>
,说明评论提交成功了,这时候data
将会返回手动刷新页面之后的HTML代码,现在要做的就是将提交后的评论显示出来。
一种方法是手动将这条评论插入,不过还要考虑页数变化等一系列问题所以很麻烦。还有一种方法是发起pjax
请求重载页面(会打断音乐播放)。
这里提出另一种傻瓜式方法:既然有了HTML代码,我们可以直接把评论列表给替换掉,很多问题就能简单的解决了!首先先在评论列表和评论分页的外部套上一层<div>
:
<div id="commentcontent">
<?php $comments->listComments(); ?>
<?php if ($comments->have()){ ?>
<?php $comments->pageNav('<i class="mdui-icon material-icons"></i>','<i class="mdui-icon material-icons"></i>',2,'···',array('wrapTag' => 'div','wrapClass' => 'page-navigator mdui-text-center','itemTag' => 'div','currentClass' => 'current')); ?>
<?php } ?>
</div>
然后就可以在提交成功之后直接将#commentcontent
中的内容替换掉:
$('#commentcontent').html($('#commentcontent',data).html());
//$('#commentcontent',data)表示在data(HTML代码)中的新#commentcontent
最后只要写一些重载代码即可(比如评论数+1)!写重载代码时,也可以利用上述方法强行替换,虽然傻瓜式,但是效果不错。
这是最后一步,定位到最新评论。
根据typecho评论的特点,如果不在评论第一页发起母评论(没有回复对象,即第一层评论),提交评论之后仍会留在当前页面。这就导致出现了两种情况:
区分这两种情况可以采用这样的方法:
commentdata
中是否含有parent
参数,如果有说明不是母评论。那么代码就是这样的:
var target='#comments',parent=true; //默认定位回评论框
$.each(commentdata,function(i,field) {if (field.name=='parent') parent=false;});
//检查是否含有parent参数
if (!parent || !$('div.page-navigator .prev').length){
//如果不是母评论,或者当前是评论第一页
//那么只要拿出所有评论中id最大的就是最新评论
var latest=-19260817;
$('#comments .mdui-panel',data).each(function(){
var id=$(this).attr('id'),coid=parseInt(id.substring(8));
if (coid>latest) {latest=coid;target='#'+id;}
});
}
最后跳转到target
所在位置即可:
$('html,body').animate({scrollTop:$(target).offset().top},'fast');
$('#comment-form').submit(function(event){
var commentdata=$(this).serializeArray();
$.ajax({
url:$(this).attr('action'),
type:$(this).attr('method'),
data:commentdata,
beforeSend:function() {$('#commentsumbit').css('display','none');$('#commenting').css('display','block');},
error:function() {mdui.alert('发生了未知错误','评论失败');$('#commenting').css('display','none');$('#commentsumbit').css('display','block');},
success:function(data){
$('#commenting').css('display','none');$('#commentsumbit').css('display','block');
var error=/<title>Error<\/title>/;
if (error.test(data)){
var text=data.match(/<div(.*?)>(.*?)<\/div>/is);
var str='发生了未知错误';if (text!=null) str=text[2];
mdui.alert(str,'评论失败');
} else {
//评论框复位(清空文本,刷新高度)
$('#commenttextarea').val('');$('#commenttextarea').css('height','');
//评论框复位(取消回复)
if ($('#cancel-comment-reply-link').css('display')!='none') $('#cancel-comment-reply-link').click();
var target='#comments',parent=true;
$.each(commentdata,function(i,field) {if (field.name=='parent') parent=false;});
if (!parent || !$('div.page-navigator .prev').length){
var latest=-19260817;
$('#comments .mdui-panel',data).each(function(){
var id=$(this).attr('id'),coid=parseInt(id.substring(8));
if (coid>latest) {latest=coid;target='#'+id;}
});
}
$('#recentcomment').html($('#recentcomment',data).html()); //更新最新评论
$('#commentsnumber').html($('#commentsnumber',data).html()); //更新评论数量
$('#commentcontent').html($('#commentcontent',data).html()); //更新评论列表
//下面是重载函数以及评论成功提示
MathJax.Hub.Typeset(document.getElementById('commentcontent'));
document.querySelectorAll('#commentcontent pre code').forEach((block) => {hljs.highlightBlock(block);});
$('#commentcontent pre code').each(function(){
var lines=$(this).text().split('\n').length;
var numbering=$('<ul/>').addClass('pre-numbering');
for(var i=1;i<=lines;i++) numbering.append($('<li/>').text(i));
$(this).addClass('has-numbering').parent().prepend(numbering);
});
mdui.mutation();$('html,body').animate({scrollTop:$(target).offset().top},'fast');
mdui.snackbar({message:'<?php echo $this->options->commentsuccess; ?>',position:'right-bottom'});
}
}
});
return false;
});
注:此方法可以方便的返回评论数据,但是兼容性较差,已经弃用。
绛木子:不使用插件实现Ajax评论功能。
QQdie:Typecho不使用插件实现Ajax评论功能。
因为我js菜爆了,网上传统的基于js的AJAX评论我并不是很能看懂QAQ。
最近发现绛木子这种比较好理解,而且容易魔改。就花了一段时间弄了一下。
的确是套(抄)上去就能用了,但是有一个很尴尬的问题……绛木子和QQdie现在用的主题都没有PJAX,而我在加上去之后和PJAX撞了……
绛木子教程里说的开启反垃圾保护是为了不验证来源页,但是不知道什么奥妙重重的原因,如果主题有PJAX,那么就算开启反垃圾保护,在切换页面之后好像依然会验证来源页,导致无法评论(或者PJAX失效)。
魔改了好久之后我还是解决不了,于是索性把:
if($archive->request->get('_') != Helper::security()->getToken($archive->request->getReferer())){
$archive->response->throwJson(array('status'=>0,'msg'=>_t('非法请求')));
}
这段给删了。结果就行了……
这个是自己瞎搞的……原理其实特智障。
先写了两个函数:
function GetCommentAt($coid){
$db=Typecho_Db::get();$fa=$db->fetchRow($db->select('coid,author')->from('table.comments')->where('coid = ?',$coid));
$content='<strong class="haveat"><a href="#comment-' . $fa['coid'] . '">@' . $fa['author'] .' </a></strong>';
return $content;
}
function RewriteComment($comment){
$content=convertSmilies($comment->content); //convertSmilies是表情转换
if ($comment->parent) $content=GetCommentAt($comment->parent) . $content;
return $content;
}
其实就是把父评论的信息从数据库里拿出来,再加到评论内容的前面。
为了将评论改为两层的,再把输出评论的部分改一下(下面给出的是框架):
<div id="<?php $comments->theId(); ?>">
<?php if ($comments->levels==0) { ?>
<div> <!-- 对于顶级评论,评论框包住子评论 -->
<?php echo RewriteComment($comments); ?> <!-- 输出转化后的评论 -->
<?php if ($comments->children) { ?>
<div class="comment-children">
<?php $comments->threadedComments($options); ?>
</div>
<?php } ?>
</div>
<?php } else { ?>
<div> <!-- 对于非顶级评论,评论框不包住子评论 -->
<?php echo RewriteComment($comments); ?>
</div>
<?php if ($comments->children) { ?>
<div class="comment-children"> <!-- 子评论还是处在comment-children中以便定位 -->
<?php $comments->threadedComments($options); ?>
</div>
<?php } ?>
<?php } ?>
</div>
然后评论就变成两层式的了,那么也就可以进行“无限”嵌套并且样式不爆炸了QAQ:
function themeInit($archive) {
Helper::options()->commentsMaxNestingLevels = 19260817; //评论"无限"层
if ($archive->is('single') && $archive->request->isPost() && $archive->request->is('themeAction=comment')) ajaxComment($archive); //AJAX评论
}
效果就是将翻页改成点击加载更多。
PS:使用后发现此方法将引起一些问题,所以我就没在用了QAQ
虽然是点击加载更多评论,但是原理和上面那篇教程里的一样。
不过因为Typecho没有单独输出评论下一页的函数,所以需要css里把除了下一页之外的东西隐藏掉……
PHP部分:
<?php $comments->pageNav('','查看更多评论',0,'',array('wrapTag' => 'div','wrapClass' => 'page-navigator','itemTag' => '','nextClass' => 'next mdui-btn mdui-btn-block mdui-ripple mdui-color-theme-accent mdui-m-b-1')); ?>
css部分:
div.page-navigator a:not([class~="next"]) {display:none;}
JS部分:
$('a.next').click(function() {
$this = $(this);$this.hide();$("#commenet-load").show();
var href = $this.attr('href');
if (href != undefined) {
$.ajax({
url: href,
type: 'get',
error: function() {mdui.alert("评论加载出错了QAQ");},
success: function(data) {
//把下一页网站里的#allcomment拿出来加入当前页的#allcomment
var $res = $(data).find('#allcomment');
$("#allcomment").append($res);mdui.mutation();
//把点击加载更多按钮的链接换成下一页的下一页
var newhref = $(data).find('a.next').attr('href');
if (newhref != undefined) {$('a.next').attr('href', newhref);}
else {$('a.next').remove();}
},
complete: function() {
$("#commenet-load").hide();$this.show();
}
});
}
return false;
});
其实这主要是篇给自己的总结文章,阅读过程中很有可能会觉得很混乱……有问题可以评论里提问,我有空(可能不太有,咕咕咕)会回复的QAQ。
测试
你那个(总的js代码)是不是用了别的jq库呀,告知下哈~
@枫叶
类似mdui.alert的函数都是mdui库中的,需要自行替换,或者引入mdui库
我也想试试你这个ajax评论效果~ |´・w・)ノ
过来取取经
趁没人发现来测试一波Twemoji
😀😁😂🤣😃😄😅😆😉😊😋😎😍😘🥰😗😙😚🙂🤗🤩🤔🤨😐😑😶🙄😏😣😥😮🤐😯😪😫😴😌😛😜😝🤤😒😓😔😕🙃🤑😲☹️🙁😖😞😟😤😢😭😦😧😨😩😬😰😱🥵🥶😳🤪😵😡😠🤬😷🤒🤕🤢🤮🤧😇🤠🥳🥴🥺🤥🤫🤭🧐🤓👻🤲👐🙌👏🤝👍👎👊✊🤛🤜🤞✌️🤟🤘👌👈👉👆👇✋🤚🖐🖖👋🤙💪🦵🦶🖕✍️🙏👀👴🐴🍋
@ZigZagK
时隔几日,我发现了!
@Bhao
这都被你发现了
我自己做的时候实践了一下,发现那些AJAX评论...
就是在主题里塞了个插件的感觉...
实际上,评论来源页错误的问题,我们只需要把评论来源页验证方法找出来,再在PJAX翻页时给重载就好了.
结果真的找到了:
这样大概能拿到一串这样的东西:
然后参考默认模板,发现他们是通过在提交表单时把这个token放进一个
_
的项里同评论一起提交的.那就很好办了.
在comments.php里把token一起输出到pjax容器里,就可以随pjax重载
_
的内容了.从而避免在主题里塞插件这种不优雅也兼容性暴跌的操作.
@Zapic
草
内容全乱了(
@Zapic
要么开个issue吧,评论太复杂解析跪了
(但是我快高考了可能没时间看,先开着吧QAQ,咕咕咕)
@ZigZagK
这个方法是彻底的大改,前端后端一起砍,开个issue可能有一种...
呃...
"你喜欢吃荔枝,我却告诉你梨子更好吃"的意思.
而且我估计解析炸了是因为我写代码块时少了几个顿点(
居然资瓷ajax评论了 赞 |´・w・)ノ
@ohmyga
好高级的说((
@ohmyga
Dalao您的Blog不是早就有了吗QAQ
(其实现在还有bug一堆 )
@ZigZagK
awsl 我是直接发送ajax请求再刷新页面的 你这直接插入评论 太高级了(
@ZigZagK
啊我这垃圾语文……我是说我的还有bug一堆QAQ
测试一下
前排兹瓷zigzag胖qwq
@可耐的菊花茶
你好好学竞赛去
测试一下