ZigZagK的博客
在PJAX的基础上使用AJAX评论等功能
2019年6月7日 01:23
Typecho
查看标签

终于把AJAX评论搞好了QwQ,效果可以在这篇文章下面评论试试(所以在本页可以随意划水2333333)。

AJAX评论

方法1:AJAX提交评论

2020.2.16 UPD:过了这么久我总算有了一点长进QAQ,现在已经基本明白了这种方法的原理。由于此方法兼容性比较好(比如不影响评论过滤插件)而且写起来更方便,所以在主题重构的时候改用了这种方式。

膜拜dalao

友人C:typecho博客实现ajax评论

其实只要对form表单提交和ajax有个大致的了解,上面这篇文章看起来就通俗易懂了QAQ。

1.关闭反垃圾保护和来源页检测

这两个功能与PJAX以及AJAX评论有冲突,需要关闭(应该是有办法支持的,不过这两个功能其实没啥用,所以关了也没事)。

可以在functions.phpthemeInit函数中加上两句话:

function themeInit($archive){
    Helper::options()->commentsAntiSpam=false; //关闭评论反垃圾
    Helper::options()->commentsCheckReferer=false; //关闭检查评论来源页URL是否与文章链接一致
}

这样就不用手动设置了。

2.改用AJAX提交评论

首先需要阻止原先的提交,改用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代码

3.提交失败时的错误信息

提交失败分为两种: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的提示框
}

4.获取新的评论列表

如果在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">&#xe314;</i>','<i class="mdui-icon material-icons">&#xe315;</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)!写重载代码时,也可以利用上述方法强行替换,虽然傻瓜式,但是效果不错。

5.定位到最新评论

这是最后一步,定位到最新评论。

根据typecho评论的特点,如果不在评论第一页发起母评论(没有回复对象,即第一层评论),提交评论之后仍会留在当前页面。这就导致出现了两种情况:

  1. 提交的评论是母评论且当前不是评论第一页。(不会跳转到最新评论)
  2. 提交的评论不是母评论,或者在第一页提交了母评论。(会跳转到最新评论)

区分这两种情况可以采用这样的方法:

  1. 检查commentdata中是否含有parent参数,如果有说明不是母评论。
  2. 检查页面中是否有评论前一页的链接,如果有说明不是评论第一页。

那么代码就是这样的:

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');

总的js代码

点击展开

$('#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;
});

方法2:改造feedback

注:此方法可以方便的返回评论数据,但是兼容性较差,已经弃用。

点击展开
该方法已弃用

膜拜dalao

绛木子:不使用插件实现Ajax评论功能

QQdie:Typecho不使用插件实现Ajax评论功能

实现方法

因为我js菜爆了,网上传统的基于js的AJAX评论我并不是很能看懂QAQ。

最近发现绛木子这种比较好理解,而且容易魔改。就花了一段时间弄了一下。

的确是套(抄)上去就能用了,但是有一个很尴尬的问题……绛木子和QQdie现在用的主题都没有PJAX,而我在加上去之后和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'] .'&nbsp;</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

膜拜dalao

枫叶:typecho 实现点击加载更多文章

实现方法

虽然是点击加载更多评论,但是原理和上面那篇教程里的一样。

不过因为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。

版权声明:本博客所有文章除特别声明外,均采用 CC BY 4.0 CN协议 许可协议。转载请注明出处!
请不要发毫无意义或内容不文明的评论。与本文无关评论请发留言板!
极简
2021-04-17 19:48:44极简
2021-04-17 19:48:44

测试

访客
2020-07-31 20:11:57枫叶
2020-07-31 20:11:57

你那个(总的js代码)是不是用了别的jq库呀,告知下哈~

访客
2020-07-31 20:46:50ZigZagK
2020-07-31 20:46:50
@枫叶 

类似mdui.alert的函数都是mdui库中的,需要自行替换,或者引入mdui库

博主
2020-07-31 19:38:49枫叶
2020-07-31 19:38:49

我也想试试你这个ajax评论效果~ |´・w・)ノ

访客
2020-03-28 14:23:49Veen Zhao
2020-03-28 14:23:49

过来取取经 向大佬低头

访客
2020-03-16 11:46:23ZigZagK
2020-03-16 11:46:23

趁没人发现来测试一波Twemoji 我的滑稽会冒汗
😀😁😂🤣😃😄😅😆😉😊😋😎😍😘🥰😗😙😚🙂🤗🤩🤔🤨😐😑😶🙄😏😣😥😮🤐😯😪😫😴😌😛😜😝🤤😒😓😔😕🙃🤑😲☹️🙁😖😞😟😤😢😭😦😧😨😩😬😰😱🥵🥶😳🤪😵😡😠🤬😷🤒🤕🤢🤮🤧😇🤠🥳🥴🥺🤥🤫🤭🧐🤓👻🤲👐🙌👏🤝👍👎👊✊🤛🤜🤞✌️🤟🤘👌👈👉👆👇✋🤚🖐🖖👋🤙💪🦵🦶🖕✍️🙏👀👴🐴🍋

博主
2020-03-24 16:37:03Bhao
2020-03-24 16:37:03
@ZigZagK 

时隔几日,我发现了! 滑天下之大稽

访客
2020-03-24 16:45:39ZigZagK
2020-03-24 16:45:39
@Bhao 

这都被你发现了

博主
2019-12-18 14:21:57Zapic
2019-12-18 14:21:57

我自己做的时候实践了一下,发现那些AJAX评论...
就是在主题里塞了个插件的感觉... 无奈.jpg

实际上,评论来源页错误的问题,我们只需要把评论来源页验证方法找出来,再在PJAX翻页时给重载就好了.
结果真的找到了:

//打乱
Typecho_Common::shuffleScriptVar(
//变token
$this->security->getToken(
//获得当前页url
$this->request->getRequestUrl()
)
);

这样大概能拿到一串这样的东西:

(function () {
var _yDX = //'CV'
'240'+//'QIL'
'2'+'772'//'pO'
+//'Jf'
'1'+'b7'//'pG'
+//'J'
'607'+/* 'D'//'D' */''+'e'//'q'
+'74'//'H'
+//'c'
'c'+'519'//'b'
+/* 'O0F'//'O0F' */''+'0'//'4c'
+''///*'2'*/'2'
+'a'//'8Dt'
+//'f'
'f'+'e0'//'jIy'
+//'0I'
'0I'+'6Zu'//'6Zu'
+'6e4'//'Cv'
+''///*'U3'*/'U3'
+//'Tm'
'fbe'+''///*'l'*/'l'
+//'Uf9'
'e'+'2M'//'2M'
+//'WUF'
'2', _4pkJK = [[16,17],[24,26],[24,27],[31,33]];

for (var i = 0; i < _4pkJK.length; i ++) {
_yDX = _yDX.substring(0, _4pkJK[i][0]) + _yDX.substring(_4pkJK[i][1]);
}

return _yDX;
})();

然后参考默认模板,发现他们是通过在提交表单时把这个token放进一个_的项里同评论一起提交的.
啥?
那就很好办了.
在comments.php里把token一起输出到pjax容器里,就可以随pjax重载_的内容了.

//输出token
<?php if ($this->options->commentsAntiSpam && $this->is('single')) {?>
<!-- AntiSpam -->
<script>
$$("#<?php echo $this->respondId;?>").append("<input type=\"hidden\" id=\"anti-spam-token\" name=\"_\" value=\"\" />");
$$(document).one("keyup touchstart mousemove",function(){
var anti_token = <?php echo Typecho_Common::shuffleScriptVar($this->security->getToken($this->request->getRequestUrl()));?>;
$$("#anti-spam-token").val(anti_token);
});
</script>
<?php } ?>

从而避免在主题里塞插件这种不优雅也兼容性暴跌的操作.
奥妙重重

访客
2019-12-18 14:22:47Zapic
2019-12-18 14:22:47
@Zapic 


内容全乱了(
恐惧

访客
2019-12-18 22:28:26ZigZagK
2019-12-18 22:28:26
@Zapic 

要么开个issue吧,评论太复杂解析跪了 无奈.jpg
(但是我快高考了可能没时间看,先开着吧QAQ,咕咕咕)

博主
2019-12-20 17:10:53Zapic
2019-12-20 17:10:53
@ZigZagK 

这个方法是彻底的大改,前端后端一起砍,开个issue可能有一种...
呃...
"你喜欢吃荔枝,我却告诉你梨子更好吃"的意思.
而且我估计解析炸了是因为我写代码块时少了几个顿点(

访客
2019-07-13 21:33:06ohmyga
2019-07-13 21:33:06

居然资瓷ajax评论了 赞 |´・w・)ノ

访客
2019-07-13 21:33:50ohmyga
2019-07-13 21:33:50
@ohmyga 

好高级的说((

访客
2019-07-14 14:53:33ZigZagK
2019-07-14 14:53:33
@ohmyga 

Dalao您的Blog不是早就有了吗QAQ
(其实现在还有bug一堆 倍感压力

博主
2019-07-15 10:26:06ohmyga
2019-07-15 10:26:06
@ZigZagK 

awsl 我是直接发送ajax请求再刷新页面的 你这直接插入评论 太高级了(

访客
2019-07-14 23:07:42ZigZagK
2019-07-14 23:07:42
@ZigZagK 

啊我这垃圾语文……我是说我的还有bug一堆QAQ

博主
2019-07-12 08:36:12初夏阳光
2019-07-12 08:36:12

测试一下 向大佬低头

访客
2019-06-17 20:39:53可耐的菊花茶
2019-06-17 20:39:53

前排兹瓷zigzag胖qwq

访客
2019-06-22 20:48:33ZigZagK
2019-06-22 20:48:33
@可耐的菊花茶 

你好好学竞赛去 快吃药

博主
2019-06-16 09:28:52ZigZagK
2019-06-16 09:28:52

测试一下 无奈.jpg

博主