再一次的给站点添加 ajax 评论体验

尚寂新
尚寂新
2019/08/12 15:49

我所周知,在全站 pjax 的情况下实现 ajax 评论是一个很炸脑袋的事情。经过了很多试验踩坑之后,最终还是采用了他的方案。
https://eriri.ink/archives/typecho-ajaxcomment.html
其实看完他的这个之后,看着注释标的也挺比较全,然后就用了这套方案,然后进行了一顿魔改之后应用到了我的站上。这套的优势就是用 Typecho 原生的接口,评论处理什么的直接使用原有的插件就可以。

下面是魔改之后的代码,需要引用 jq 库!
本文需要阅读者拥有一定的折腾能力,纯小白、纯萌新建议绕路

function ajax_edit_ver () {
    var replyTo = ''   //回复评论时候的ID
    submitButton = $(".submit").eq(0),  //提交评论按钮
    commentForm = $("#comment-form"),   //评论表单
    newCommentId = "",   //新评论的ID
    //console.log('调用bind');
    $(".js-comment-reply a").click(function () {
        //console.log('已触发点击回复');
        replyTo = $(this).parent().parent().attr("id");
        //console.log(replyTo);
    });
    $(".js-cancel-comment-reply a").click(function () { /*console.log('已触发取消回复')*/;replyTo = ''; });
    //console.log("bind button");
 
    /**
     * 评论数目加一
     */
    function commentCounts() {
        var counts = parseInt($("#comment-ajax-plus-plus").text());
        $("#comment-ajax-plus-plus").html($("#comment-ajax-plus-plus").html().replace(/\d+/, counts + 1));
    };
 
    /**
     * 发送前的处理
     */
    function beforeSendComment() {
        submitButton.attr("disabled", true).css('cursor', 'not-allowed');
        commentForm.css({ 'opacity': '0.5' });
        $("input,textarea", commentForm).attr('disabled', true);
    }
 
    /**
     * 发送后的处理
     * @param {boolean} ok 
     */
    function afterSendComment(ok) {
        submitButton.attr("disabled", false).css('cursor', 'pointer');
        commentForm.css({ 'opacity': '1' });
        //$("textarea", commentForm).css({ 'background': 'initial' });
        $("input,textarea", commentForm).attr('disabled', false);
        if (ok) {
            $("#textarea").val('');
            replyTo = '';
        }
        
        //console.log('调用bind');
        $(".comment-reply a").click(function () {
            replyTo = $(this).parent().parent().attr("id");
            //console.log(replyTo);
        });
        $(".cancel-comment-reply a").click(function () { replyTo = ''; });
        //console.log("bind button");
        
    }
 
    $("#comment-form").submit(function () {
        commentData = $(this).serializeArray();
        
        /** 准备发送数据 */
        beforeSendComment();
        $.ajax({
            type: $(this).attr('method'),
            url: $(this).attr('action'),
            data: commentData,
            error: function (e) {
                console.log('Ajax Comment Error');
                window.location.reload();
            },
            success: function (data) {
                if (!$('#comments', data).length) {
                    var msg = $('title').eq(0).text().trim().toLowerCase() === 'error' ?  $('.container', data).eq(0).text() : '评论提交失败!请检查你输入的内容!';
                    swal("返回错误", msg, "error");
                    //alert(msg);
                    //console.log(msg);
                    afterSendComment(false);
                    return false;
                }
 
                $("#textarea").val('');
                /** 炸脑袋的 */
                var newComment;
                /** 获取新评论的id */
                newCommentId = $(".comment-list", data).html().match(/id=\"?comment-\d+/g).join().match(/\d+/g).sort(function (a, b) { return a - b }).pop();
                /** 处理父级评论 */
                if('' === replyTo) {
                    if(!$('.comment-list').length) {
                        /** 本博含有零评论提示,本部分无用已精简(无评论结构需与评论单元格式基本一致) */
                    }
                    else if($('.prev').length) {
                        $('.page-navigator li a').eq(1).click();
                    }
                    else {
                        newComment  = $("#li-comment-" + newCommentId, data);
                        $('.comment-list').first().prepend((newComment));
                        $('#no-comment-placeholder').remove();
                    }
                    //$('html,body').animate({scrollTop:$('#response').offset().top - 100},1000);
                }
                /** 处理子评论 */
                else {
                    //取数据
                    newComment = $("#li-comment-" + newCommentId, data);
                    if ($('#li-' + replyTo).find('.comment-children').length) {
                        //已存在.comment-children 直接插入新数据
                        $('#li-' + replyTo + ' .comment-children .comment-list').prepend((newComment));
                        TypechoComment.cancelReply();
                    }
                    else {
                        //创建一个.comment-children
                        $('#li-' + replyTo).append('<div class="comment-children"><ol class="comment-list"></ol></div>');
                        $('#li-' + replyTo + ' .comment-children .comment-list').first().prepend((newComment));
                        TypechoComment.cancelReply();
                    }
                }
                commentCounts();
                afterSendComment(true);
                //alert('评论提交成功!如首次在本站评论,需要等待审核!');
                swal("评论成功", "如首次在本站评论,需要等待审核!", "success");
            }
        });
        return false;
    });
}
ajax_edit_ver ();

讲解

L3-L4:需要自行按照主题模板进行适配。
L9:需要定位父元素。一个.parent()相当于往前推一个标签。

<li id="li-comment-107" class="comment-container   comment-even ">
<div id="comment-107">
    <img src="https://gravatar.loli.net/avatar/eda06ebc12b16108a945e2755c5df7bc?s=35&amp;r=G&amp;d=identicon" class="avatar">
    <div class="reply js-comment-reply"><a href="//shangjixin.com/links.html?replyTo=107#respond-page-64" rel="nofollow" onclick="return TypechoComment.reply('comment-107', 107);">回复</a></div>
    <div class="info">
        <div class="name"><a href="https://jimoe.cn/" rel="external nofollow">尚寂新</a> <span class="sdblue" aria-hidden="true" style="cursor:pointer;" title="喵~">博主</span></div>
        <div class="time">17-10-03 07:37&nbsp;@&nbsp;Android 5.1</div>
        <div class="desc"><p> 友情链接一直招收中哦</p></div>
    </div>
</div>
</li>

就比如说,我的评论结构是这样的,第四行中有一个回复按钮,抓住它。包裹着他的<div>标签需要定义class(不要定义为id,因为id在一个页面只能有一个同名的)然后把这个在主代码那块 L7 那块标好,定位回复按钮。本示例往外推 3 层就可以定位到comment-107了,所以说.parent()要写三个。L12 同理,只是不需要判断.parent()
评论数+1:可以参照本博,把数字拿标签包起来,然后换进去就行了(这个应该不用多解释)。
发送前的处理:无关紧要,主要就是变一些 css 什么的,如果不需要,L27-L29 均可注释掉。
发送后的处理:同上。但 L37-L40 才只是包括样式的变换。L41-L51 均为复位的操作。
L72-L74 以及 L117-L118:评论失败/成功的通知。swal()是一个 jq 提示框插件,如果不想装的话可以换成 js 的原生方式,或者换用其他提示框。(更换的话不用多说了吧)。
L87:内容我已经省略,需要的话找下原帖就可以(在文章开始的时候引用过一遍(偷懒.jpg)(提示:他那边行数跟我这边肯定不是对应的,因为我这个魔改的跟原版有区别)需要像我那么弄的话,在commments.php里判断有无评论的地方加进去个else之后,依照评论结构为模板(如上方L9的讲解那样),不要变动外两层,基本上都能认出来。
我的子评论嵌入位置为上面 L9 实例那个 L8 的后面,这样更有利于 ajax 评论跟页面初加载的时候效果一致,但也需要用 css 把子评论的效果改一下 实际上就是套娃样式,css 改一改就不一样了
L124:所谓的 ajax/pjax 回调代码。这个实际意义是把上面那一大堆都给执行一遍。就这样
差不多就是这样。如果你实在是再想偷懒的话(我刚开始就是这么搞的,当时脑袋都折磨炸了,js 一点基础都没有)L81-L144 全部删掉即可。删掉之后,即使评论成功了,评论内容也不会更新跟上来(实际上内容以及成功添加,入库了),但页面是无刷新的。
L90:评论分页存在时,往前翻一页,让评论者看到新评论,同样需要进行适配。不需要的话同样可以打上注释,或者是没开评论分页的话,这行其实都可以不太用管。
嗯,如果站点存在全站 ajax 或 pjax 的话, 刷新区内原生评论定位用的 js (TypechoComment)还是要添加的。具体如下(别放在单独的 .js 静态文件里,因为里面有 php 代码)

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

好的 差不多就是这样了qwq

已有 2 条评论 (旧评论在前)
  1. 泽泽 友链
    回复
    2019-08-22 18:26 Windows 7

    我之所以不用纯js的ajax评论,就是因为没办法准确输出评论拦截插件的报错信息

  2. Kiosr
    回复
    2019-09-17 09:48 iOS 12.3.1

    我也是这么实现的,新评论就在301返回里取,旧评论会取主评论的当前评论页进行二次请求,对于回复一页之前的评论,都会有个二次请求,因为301只有第一页的评论

添加新评论 (Markdown Supported)
(ノ°ο°)ノ