typecho实现Ajax评论功能
admin
撰写于 2021年 02月 20 日

为了不使用插件实现Ajax评论功能需要实现:

监听评论表单,改用ajax方式提交
创建新的评论表单提交地址(用Typecho 主题提供的系统方法themeInit实现)
当访问文章加载主题时,themeInit方法首先被加载,可在此方法中判断是否为添加评论的操作,即新的评论表单地址为文章的链接(permalink).具体判断方法如下

// 主题初始化
function themeInit($archive){
// 判断是否是添加评论的操作
// 为文章或页面、post操作,且包含参数`themeAction=comment`(自定义)
if($archive->is('single') && $archive->request->isPost() && $archive->request->is('themeAction=comment')){
    // 为添加评论的操作时
    ajaxComment($archive);
}
}

要实现ajax评论,则无法使用系统默认的feedback,这里我们将复制feedback功能并改造为我们需要的方法

/**
* ajaxComment
* 实现Ajax评论的方法(实现feedback中的comment功能)
* @param Widget_Archive $archive
* @return void
*/
function ajaxComment($archive){
$options = Helper::options();
$user = Typecho_Widget::widget('Widget_User');
$db = Typecho_Db::get();
// Security 验证不通过时会直接跳转,所以需要自己进行判断
// 需要开启反垃圾保护,此时将不验证来源
if($archive->request->get('_') != Helper::security()->getToken($archive->request->getReferer())){
    $archive->response->throwJson(array('status'=>0,'msg'=>_t('非法请求')));
}
/** 评论关闭 */
if(!$archive->allow('comment')){
    $archive->response->throwJson(array('status'=>0,'msg'=>_t('评论已关闭')));
}
/** 检查ip评论间隔 */
if (!$user->pass('editor', true) && $archive->authorId != $user->uid &&
$options->commentsPostIntervalEnable){
    $latestComment = $db->fetchRow($db->select('created')->from('table.comments')
                ->where('cid = ?', $archive->cid)
                ->where('ip = ?', $archive->request->getIp())
                ->order('created', Typecho_Db::SORT_DESC)
                ->limit(1));

    if ($latestComment && ($options->gmtTime - $latestComment['created'] > 0 &&
    $options->gmtTime - $latestComment['created'] < $options->commentsPostInterval)) {
        $archive->response->throwJson(array('status'=>0,'msg'=>_t('对不起, 您的发言过于频繁, 请稍侯再次发布')));
    }        
}

$comment = array(
    'cid'       =>  $archive->cid,
    'created'   =>  $options->gmtTime,
    'agent'     =>  $archive->request->getAgent(),
    'ip'        =>  $archive->request->getIp(),
    'ownerId'   =>  $archive->author->uid,
    'type'      =>  'comment',
    'status'    =>  !$archive->allow('edit') && $options->commentsRequireModeration ? 'waiting' : 'approved'
);

/** 判断父节点 */
if ($parentId = $archive->request->filter('int')->get('parent')) {
    if ($options->commentsThreaded && ($parent = $db->fetchRow($db->select('coid', 'cid')->from('table.comments')
    ->where('coid = ?', $parentId))) && $archive->cid == $parent['cid']) {
        $comment['parent'] = $parentId;
    } else {
        $archive->response->throwJson(array('status'=>0,'msg'=>_t('父级评论不存在')));
    }
}
$feedback = Typecho_Widget::widget('Widget_Feedback');
//检验格式
$validator = new Typecho_Validate();
$validator->addRule('author', 'required', _t('必须填写用户名'));
$validator->addRule('author', 'xssCheck', _t('请不要在用户名中使用特殊字符'));
$validator->addRule('author', array($feedback, 'requireUserLogin'), _t('您所使用的用户名已经被注册,请登录后再次提交'));
$validator->addRule('author', 'maxLength', _t('用户名最多包含200个字符'), 200);

if ($options->commentsRequireMail && !$user->hasLogin()) {
    $validator->addRule('mail', 'required', _t('必须填写电子邮箱地址'));
}

$validator->addRule('mail', 'email', _t('邮箱地址不合法'));
$validator->addRule('mail', 'maxLength', _t('电子邮箱最多包含200个字符'), 200);

if ($options->commentsRequireUrl && !$user->hasLogin()) {
    $validator->addRule('url', 'required', _t('必须填写个人主页'));
}
$validator->addRule('url', 'url', _t('个人主页地址格式错误'));
$validator->addRule('url', 'maxLength', _t('个人主页地址最多包含200个字符'), 200);

$validator->addRule('text', 'required', _t('必须填写评论内容'));

$comment['text'] = $archive->request->text;

/** 对一般匿名访问者,将用户数据保存一个月 */
if (!$user->hasLogin()) {
    /** Anti-XSS */
    $comment['author'] = $archive->request->filter('trim')->author;
    $comment['mail'] = $archive->request->filter('trim')->mail;
    $comment['url'] = $archive->request->filter('trim')->url;

    /** 修正用户提交的url */
    if (!empty($comment['url'])) {
        $urlParams = parse_url($comment['url']);
        if (!isset($urlParams['scheme'])) {
            $comment['url'] = 'http://' . $comment['url'];
        }
    }

    $expire = $options->gmtTime + $options->timezone + 30*24*3600;
    Typecho_Cookie::set('__typecho_remember_author', $comment['author'], $expire);
    Typecho_Cookie::set('__typecho_remember_mail', $comment['mail'], $expire);
    Typecho_Cookie::set('__typecho_remember_url', $comment['url'], $expire);
} else {
    $comment['author'] = $user->screenName;
    $comment['mail'] = $user->mail;
    $comment['url'] = $user->url;

    /** 记录登录用户的id */
    $comment['authorId'] = $user->uid;
}

/** 评论者之前须有评论通过了审核 */
if (!$options->commentsRequireModeration && $options->commentsWhitelist) {
    if ($feedback->size($feedback->select()->where('author = ? AND mail = ? AND status = ?', $comment['author'], $comment['mail'], 'approved'))) {
        $comment['status'] = 'approved';
    } else {
        $comment['status'] = 'waiting';
    }
}

if ($error = $validator->run($comment)) {
    $archive->response->throwJson(array('status'=>0,'msg'=> implode(';',$error)));
}

/** 添加评论 */
$commentId = $feedback->insert($comment);
if(!$commentId){
    $archive->response->throwJson(array('status'=>0,'msg'=>_t('评论失败')));
}
Typecho_Cookie::delete('__typecho_remember_text');
$db->fetchRow($feedback->select()->where('coid = ?', $commentId)
->limit(1), array($feedback, 'push'));
// 返回评论数据
$data = array(
    'cid' => $feedback->cid,
    'coid' => $feedback->coid,
    'parent' => $feedback->parent,
    'mail' => $feedback->mail,
    'url' => $feedback->url,
    'ip' => $feedback->ip,
    'agent' => $feedback->agent,
    'author' => $feedback->author,
    'authorId' => $feedback->authorId,
    'permalink' => $feedback->permalink,
    'created' => $feedback->created,
    'datetime' => $feedback->date->format('Y-m-d H:i:s'),
    'status' => $feedback->status,
);
// 评论内容
ob_start();
$feedback->content();
$data['content'] = ob_get_clean();

$data['avatar'] = Typecho_Common::gravatarUrl($data['mail'], 48, Helper::options()->commentsAvatarRating, NULL, $archive->request->isSecure());
$archive->response->throwJson(array('status'=>1,'comment'=>$data));
}

当已经在functions.php文件中添加完上述方法时,就已经可以接收ajax评论了。
此时我们需要修改评论表单添加的方式及提交的地址。

在footer.php文件中添加方法

// 依赖jquery,请自行加载
$(function(){
// 监听评论表单提交
$('#comment-form').submit(function(){
    var form = $(this), params = form.serialize();
    // 添加functions.php中定义的判断参数
    params += '&themeAction=comment';
    
    // 解析新评论并附加到评论列表
    var appendComment = function(comment){
        // 评论列表
        var el = $('#comments > .comment-list');
        if(0 != comment.parent){
            // 子评论则重新定位评论列表
            var el = $('#comment-'+comment.parent);
            // 父评论不存在子评论时
            if(el.find('.comment-children').length < 1){
                $('<div class="comment-children"><ol class="comment-list"></ol></div>').appendTo(el);
            }else if(el.find('.comment-children > .comment-list').length <1){
                $('<ol class="comment-list"></ol>').appendTo(el.find('.comment-children'));
            }
            el = $('#comment-'+comment.parent).find('.comment-children').find('.comment-list');
        }
        if(0 == el.length){
            $('<ol class="comment-list"></ol>').appendTo($('#comments'));
            el = $('#comments > .comment-list');
        }
                    // 评论html模板,根据具体主题定制
        var html = '<li id="comment-{coid}" class="comment-body comment-ajax"><div class="comment-author"><span><img class="avatar" src="{avatar}" alt="{author}" width="32" height="32"></span><cite class="fn">{author}</cite></div><div class="comment-meta"><a href="{permalink}"><time>{datetime}</time></a></div><div class="comment-content">{content}</div></li>';
        $.each(comment,function(k,v){
            regExp = new RegExp('{'+k+'}', 'g');
            html = html.replace(regExp, v);
        });
        $(html).appendTo(el);
    }
    // ajax提交评论
    $.ajax({
        url: '<?php $this->permalink();?>',
        type: 'POST',
        data: params,
        dataType: 'json',
        beforeSend: function() { form.find('.submit').addClass('loading').html('<i class="icon icon-loading icon-pulse"></i> 提交中...').attr('disabled','disabled');},
        complete: function() { form.find('.submit').removeClass('loading').html('提交评论').removeAttr('disabled');},
        success: function(result){
            if(1 == result.status){
                // 新评论附加到评论列表
                appendComment(result.comment);
                form.find('textarea').val('');
            }else{
                // 提醒错误消息
                alert(undefined === result.msg ? '<?php _e('评论出错!');?>' : result.msg);
            }
        },
        error:function(xhr, ajaxOptions, thrownError){
            alert('评论失败,请重试');
        }
    });
    return false;
});
});

ajax评论需要自定义评论模板,获取使用其他方式拼接评论html。

评论form表单的提交按钮需要添加class="submit"或修改代码为其他自定义的class

注:需开启评论的反垃圾保护

(本文转自绛木子) 具体使用方式还未去实现,只是为了存资料,方便以后学习

typecho实现Ajax评论功能

为了不使用插件实现Ajax评论功能需要实现:

监听评论表单,改用ajax方式提交
创建新的评论表单提交地址(用Typecho 主题提供的系统方法themeInit实现)
当访问文章加载主题时,themeInit方法首先被加载,可在此方法中判断是否为添加评论的操作,即新的评论表单地址为文章的链接(permalink).具体判断方法如下

// 主题初始化
function themeInit($archive){
// 判断是否是添加评论的操作
// 为文章或页面、post操作,且包含参数`themeAction=comment`(自定义)
if($archive->is('single') && $archive->request->isPost() && $archive->request->is('themeAction=comment')){
    // 为添加评论的操作时
    ajaxComment($archive);
}
}

要实现ajax评论,则无法使用系统默认的feedback,这里我们将复制feedback功能并改造为我们需要的方法

/**
* ajaxComment
* 实现Ajax评论的方法(实现feedback中的comment功能)
* @param Widget_Archive $archive
* @return void
*/
function ajaxComment($archive){
$options = Helper::options();
$user = Typecho_Widget::widget('Widget_User');
$db = Typecho_Db::get();
// Security 验证不通过时会直接跳转,所以需要自己进行判断
// 需要开启反垃圾保护,此时将不验证来源
if($archive->request->get('_') != Helper::security()->getToken($archive->request->getReferer())){
    $archive->response->throwJson(array('status'=>0,'msg'=>_t('非法请求')));
}
/** 评论关闭 */
if(!$archive->allow('comment')){
    $archive->response->throwJson(array('status'=>0,'msg'=>_t('评论已关闭')));
}
/** 检查ip评论间隔 */
if (!$user->pass('editor', true) && $archive->authorId != $user->uid &&
$options->commentsPostIntervalEnable){
    $latestComment = $db->fetchRow($db->select('created')->from('table.comments')
                ->where('cid = ?', $archive->cid)
                ->where('ip = ?', $archive->request->getIp())
                ->order('created', Typecho_Db::SORT_DESC)
                ->limit(1));

    if ($latestComment && ($options->gmtTime - $latestComment['created'] > 0 &&
    $options->gmtTime - $latestComment['created'] < $options->commentsPostInterval)) {
        $archive->response->throwJson(array('status'=>0,'msg'=>_t('对不起, 您的发言过于频繁, 请稍侯再次发布')));
    }        
}

$comment = array(
    'cid'       =>  $archive->cid,
    'created'   =>  $options->gmtTime,
    'agent'     =>  $archive->request->getAgent(),
    'ip'        =>  $archive->request->getIp(),
    'ownerId'   =>  $archive->author->uid,
    'type'      =>  'comment',
    'status'    =>  !$archive->allow('edit') && $options->commentsRequireModeration ? 'waiting' : 'approved'
);

/** 判断父节点 */
if ($parentId = $archive->request->filter('int')->get('parent')) {
    if ($options->commentsThreaded && ($parent = $db->fetchRow($db->select('coid', 'cid')->from('table.comments')
    ->where('coid = ?', $parentId))) && $archive->cid == $parent['cid']) {
        $comment['parent'] = $parentId;
    } else {
        $archive->response->throwJson(array('status'=>0,'msg'=>_t('父级评论不存在')));
    }
}
$feedback = Typecho_Widget::widget('Widget_Feedback');
//检验格式
$validator = new Typecho_Validate();
$validator->addRule('author', 'required', _t('必须填写用户名'));
$validator->addRule('author', 'xssCheck', _t('请不要在用户名中使用特殊字符'));
$validator->addRule('author', array($feedback, 'requireUserLogin'), _t('您所使用的用户名已经被注册,请登录后再次提交'));
$validator->addRule('author', 'maxLength', _t('用户名最多包含200个字符'), 200);

if ($options->commentsRequireMail && !$user->hasLogin()) {
    $validator->addRule('mail', 'required', _t('必须填写电子邮箱地址'));
}

$validator->addRule('mail', 'email', _t('邮箱地址不合法'));
$validator->addRule('mail', 'maxLength', _t('电子邮箱最多包含200个字符'), 200);

if ($options->commentsRequireUrl && !$user->hasLogin()) {
    $validator->addRule('url', 'required', _t('必须填写个人主页'));
}
$validator->addRule('url', 'url', _t('个人主页地址格式错误'));
$validator->addRule('url', 'maxLength', _t('个人主页地址最多包含200个字符'), 200);

$validator->addRule('text', 'required', _t('必须填写评论内容'));

$comment['text'] = $archive->request->text;

/** 对一般匿名访问者,将用户数据保存一个月 */
if (!$user->hasLogin()) {
    /** Anti-XSS */
    $comment['author'] = $archive->request->filter('trim')->author;
    $comment['mail'] = $archive->request->filter('trim')->mail;
    $comment['url'] = $archive->request->filter('trim')->url;

    /** 修正用户提交的url */
    if (!empty($comment['url'])) {
        $urlParams = parse_url($comment['url']);
        if (!isset($urlParams['scheme'])) {
            $comment['url'] = 'http://' . $comment['url'];
        }
    }

    $expire = $options->gmtTime + $options->timezone + 30*24*3600;
    Typecho_Cookie::set('__typecho_remember_author', $comment['author'], $expire);
    Typecho_Cookie::set('__typecho_remember_mail', $comment['mail'], $expire);
    Typecho_Cookie::set('__typecho_remember_url', $comment['url'], $expire);
} else {
    $comment['author'] = $user->screenName;
    $comment['mail'] = $user->mail;
    $comment['url'] = $user->url;

    /** 记录登录用户的id */
    $comment['authorId'] = $user->uid;
}

/** 评论者之前须有评论通过了审核 */
if (!$options->commentsRequireModeration && $options->commentsWhitelist) {
    if ($feedback->size($feedback->select()->where('author = ? AND mail = ? AND status = ?', $comment['author'], $comment['mail'], 'approved'))) {
        $comment['status'] = 'approved';
    } else {
        $comment['status'] = 'waiting';
    }
}

if ($error = $validator->run($comment)) {
    $archive->response->throwJson(array('status'=>0,'msg'=> implode(';',$error)));
}

/** 添加评论 */
$commentId = $feedback->insert($comment);
if(!$commentId){
    $archive->response->throwJson(array('status'=>0,'msg'=>_t('评论失败')));
}
Typecho_Cookie::delete('__typecho_remember_text');
$db->fetchRow($feedback->select()->where('coid = ?', $commentId)
->limit(1), array($feedback, 'push'));
// 返回评论数据
$data = array(
    'cid' => $feedback->cid,
    'coid' => $feedback->coid,
    'parent' => $feedback->parent,
    'mail' => $feedback->mail,
    'url' => $feedback->url,
    'ip' => $feedback->ip,
    'agent' => $feedback->agent,
    'author' => $feedback->author,
    'authorId' => $feedback->authorId,
    'permalink' => $feedback->permalink,
    'created' => $feedback->created,
    'datetime' => $feedback->date->format('Y-m-d H:i:s'),
    'status' => $feedback->status,
);
// 评论内容
ob_start();
$feedback->content();
$data['content'] = ob_get_clean();

$data['avatar'] = Typecho_Common::gravatarUrl($data['mail'], 48, Helper::options()->commentsAvatarRating, NULL, $archive->request->isSecure());
$archive->response->throwJson(array('status'=>1,'comment'=>$data));
}

当已经在functions.php文件中添加完上述方法时,就已经可以接收ajax评论了。
此时我们需要修改评论表单添加的方式及提交的地址。

在footer.php文件中添加方法

// 依赖jquery,请自行加载
$(function(){
// 监听评论表单提交
$('#comment-form').submit(function(){
    var form = $(this), params = form.serialize();
    // 添加functions.php中定义的判断参数
    params += '&themeAction=comment';
    
    // 解析新评论并附加到评论列表
    var appendComment = function(comment){
        // 评论列表
        var el = $('#comments > .comment-list');
        if(0 != comment.parent){
            // 子评论则重新定位评论列表
            var el = $('#comment-'+comment.parent);
            // 父评论不存在子评论时
            if(el.find('.comment-children').length < 1){
                $('<div class="comment-children"><ol class="comment-list"></ol></div>').appendTo(el);
            }else if(el.find('.comment-children > .comment-list').length <1){
                $('<ol class="comment-list"></ol>').appendTo(el.find('.comment-children'));
            }
            el = $('#comment-'+comment.parent).find('.comment-children').find('.comment-list');
        }
        if(0 == el.length){
            $('<ol class="comment-list"></ol>').appendTo($('#comments'));
            el = $('#comments > .comment-list');
        }
                    // 评论html模板,根据具体主题定制
        var html = '<li id="comment-{coid}" class="comment-body comment-ajax"><div class="comment-author"><span><img class="avatar" src="{avatar}" alt="{author}" width="32" height="32"></span><cite class="fn">{author}</cite></div><div class="comment-meta"><a href="{permalink}"><time>{datetime}</time></a></div><div class="comment-content">{content}</div></li>';
        $.each(comment,function(k,v){
            regExp = new RegExp('{'+k+'}', 'g');
            html = html.replace(regExp, v);
        });
        $(html).appendTo(el);
    }
    // ajax提交评论
    $.ajax({
        url: '<?php $this->permalink();?>',
        type: 'POST',
        data: params,
        dataType: 'json',
        beforeSend: function() { form.find('.submit').addClass('loading').html('<i class="icon icon-loading icon-pulse"></i> 提交中...').attr('disabled','disabled');},
        complete: function() { form.find('.submit').removeClass('loading').html('提交评论').removeAttr('disabled');},
        success: function(result){
            if(1 == result.status){
                // 新评论附加到评论列表
                appendComment(result.comment);
                form.find('textarea').val('');
            }else{
                // 提醒错误消息
                alert(undefined === result.msg ? '<?php _e('评论出错!');?>' : result.msg);
            }
        },
        error:function(xhr, ajaxOptions, thrownError){
            alert('评论失败,请重试');
        }
    });
    return false;
});
});

ajax评论需要自定义评论模板,获取使用其他方式拼接评论html。

评论form表单的提交按钮需要添加class="submit"或修改代码为其他自定义的class

注:需开启评论的反垃圾保护

(本文转自绛木子) 具体使用方式还未去实现,只是为了存资料,方便以后学习

那年今日
02月
20日
赞 (1)

猜您想看

评论区(2条评论)

我要评论


不是睡觉
LV1

看看吧

sfds
LV1

试试::(呵呵)