前言
最近在写 Miracle! 主题(Miracles 主题 2.0 版本,付费),觉得 typecho 的评论嵌套一层一层套下去很难受,而且评论框还没办法直接放到评论列表前面(我之前是用 flex 布局,从 css 层面调换了两个 div 的位置)。于是,参考了几个大佬的代码,从主题层面对 Typecho 的评论进行了一些改造,让所有的子评论都嵌套在一层。这样做了之后对评论 ajax 提交又带来了一些问题,摸索了好久终于实现了想要的效果。
我花在评论功能上的时间还比较多,所以就想来写篇文章记录一下倒腾的过程,当作一篇教程吧。
改造嵌套方式
首先在主题目录下新建一个 PHP 文件,比如Comments.php
,在 functions.php
中引入。
require_once('libs/Comments.php');
在新建的 Comments.php
创建一个类(Class),继承 Typecho 的 Widget_Abstract_Comments
类。
/**
* 评论归档组件
*
* @category typecho
* @package Widget
* @copyright Copyright (c) 2008 Typecho team (http://www.typecho.org)
* @license GNU General Public License 2.0
*/
/* 因为代码大部分来自 Typecho,所以要开源或商用的话,最好留下 Typecho 的版权信息 */
class Miracle_Comments_Archive extends Widget_Abstract_Comments
{
//接下来的代码写在这里
}
接下来的内容几乎都是直接复制 Typecho 评论区的源代码,做了一些小修改,包括评论列表的结构也在这里定义了,请按照实际情况进行修改(通常只需要修改评论列表结构那一部分就好了),这些代码在我修改前来自 @AlanDecode
和 @bakaomg (ohmyga)
。
/**
* 当前页
*
* @access private
* @var integer
*/
private $_currentPage;
/**
* 所有文章个数
*
* @access private
* @var integer
*/
private $_total = false;
/**/
* 子父级评论关系
*
* @access private
* @var array
*/
private $_threadedComments = array();
/**
* 多级评论回调函数
*
* @access private
* @var mixed
*/
private $_customThreadedCommentsCallback = false;
/**
* _singleCommentOptions
*
* @var mixed
* @access private
*/
private $_singleCommentOptions = NULL;
/**
* 安全组件
*/
private $_security = NULL;
private $_commentAuthors = array();
/**
* 构造函数,初始化组件
*
* @access public
* @param mixed $request request对象
* @param mixed $response response对象
* @param mixed $params 参数列表
* @return void
*/
public function __construct($request, $response, $params = NULL) {
parent::__construct($request, $response, $params);
$this->parameter->setDefault('parentId=0&commentPage=0&commentsNum=0&allowComment=1');
Typecho_Widget::widget('Widget_Security')->to($this->_security);
/** 初始化回调函数 */
if (function_exists('threadedComments')) {
$this->_customThreadedCommentsCallback = true;
}
}
/**
* 评论回调函数
*
* @access private
* @return void
*/
private function threadedCommentsCallback() {
$singleCommentOptions = $this->_singleCommentOptions;
if (function_exists('threadedComments')) {
return threadedComments($this, $singleCommentOptions);
}
$commentClass = '';
$commentOwner = '';
if ($this->authorId) {
if ($this->authorId == $this->ownerId) {
$commentClass .= ' comment-by-author';
$commentOwner .= '<div class="comment-by-author"></div>';
} else {
$commentClass .= ' comment-by-user';
}
}else{
$commentClass .= ' comment-by-guest';
}
if ($this->levels > 0) {
$commentClass .= ' comment-child';
} else {
$commentClass .= ' comment-parent';
}
//评论结构
?>
<div id="<?php $this->theId(); ?>" class="comments-box<?php echo $commentClass; ?>">
<main class="comments-content-wrap">
<div class="comments-avatar-box">
<img class="comments-avatar" src="<?php echo Utils::gravatar($this->mail); ?>" /><?php echo $commentOwner;//这个 Utils::gravatar 方法并没有在 Typecho 中被定义,使用时请替换成其他的方法 ?>
</div>
<div class="comments-content">
<div class="comments-author">
<?php if(!empty($this->url)) {
echo '<a href="'.$this->url.'" target="_blank">'.$this->author.'</a>';
}else{
echo '<span>'.$this->author.'</span>';
} ?>
<small class="comments-reply-info"><?php if($this->getParent()){echo $GLOBALS['lang_comment']['reply_to']; echo $this->getParent(); //这里前面的全局变量是 Miracle 主题里的语言包,请按照实际情况替换成纯文本,后面的也一样 ?> · <?php echo Utils::parseCommentDate($this->created);}//这里的 Utils::parseCommentDate 是我在主题中定义的,请替换成其他的方法(用于输出评论的日期) ?></small>
</div>
<div class="comments-info">
<div class="comments-meta">
<?php echo Utils::parseCommentDate($this->created); ?>
<?php if ('waiting' == $this->status) { ?> <span class="comments-status"><?php if(!$this->user->hasLogin()) { echo $GLOBALS['lang_comment']['status_info']['logout']; }else{ echo $GLOBALS['lang_comment']['status_info']['login']; } ?></span><?php } ?>
</div>
<div class="comments-reply">
<?php $this->reply('<button class="comments-reply-btn hint--bottom" aria-label="'.$GLOBALS['lang_comment']['reply_to'].' @'.$this->author.'">'.$GLOBALS['lang_comment']['reply_btn'].'</button>'); //这里的 aria-label 是我主题内置的一个显示 tooltip 的属性,请按照实际情况删除或替换 ?>
</div>
<div class="comments-text textretty">
<?php echo Contents::parseEmo($this->content); //这个 Contents::parseEmo 方法是用于解析 owo 表情的,并非 typecho 自带,实际使用请删除或替换 ?>
</div>
</div>
</div>
</main>
<?php if ($this->children) { ?> <div class="comment-children" itemprop="discusses">
<?php $this->threadedComments(); ?>
</div><?php } ?>
</div>
<?php
}
/**
* 获取被回复者
*/
private function getParent() {
$db = Typecho_Db::get();
$parentID = $db->fetchRow($db->select()->from('table.comments')->where('coid = ?', $this->coid));
$parentID = $parentID['parent'];
if($parentID == '0') {
return '';
} else {
$author=$db->fetchRow($db->select()->from('table.comments')->where('coid = ?', $parentID));
$link = 'href="#comment-'.$author['coid'].'"';
//如果是删除的评论
if (!array_key_exists('author', $author) || empty($author['author'])) {
$author['author'] = $GLOBALS['lang_comment']['deleted'];
$link = '';
} elseif (!empty($author['author']) && @$author['status'] == 'waiting'){
//如果是带审核的评论
if(!$this->user->hasLogin()) {
$author['author'] = $GLOBALS['lang_comment']['waiting'];
$link = '';
}else{
$author['author'] = $author['author'];
}
}
if (!$link) {
return '<a class="comment-reply-at" no-go>@'.$author['author'].'</a> ';
}else{
return '<a '.$link.' class="comment-reply-at" no-go>@'.$author['author'].'</a> ';
}
}
}
/**
* 获取当前评论链接
*
* @access protected
* @return string
*/
protected function ___permalink() {
if ($this->options->commentsPageBreak) {
$pageRow = array('permalink' => $this->parentContent['pathinfo'], 'commentPage' => $this->_currentPage);
return Typecho_Router::url('comment_page',
$pageRow, $this->options->index) . '#' . $this->theId;
}
return $this->parentContent['permalink'] . '#' . $this->theId;
}
/**
* 子评论
*
* @access protected
* @return array
*/
protected function ___children() {
return $this->options->commentsThreaded && !$this->isTopLevel && isset($this->_threadedComments[$this->coid])
? $this->_threadedComments[$this->coid] : array();
}
/**
* 是否到达顶层
*
* @access protected
* @return boolean
*/
protected function ___isTopLevel() {
return $this->levels > 0;
}
/**
* 重载内容获取
*
* @access protected
* @return void
*/
protected function ___parentContent() {
return $this->parameter->parentContent;
}
/**
* 输出文章评论数
*
* @access public
* @param string $string 评论数格式化数据
* @return void
*/
public function num() {
$args = func_get_args();
if (!$args) {
$args[] = '%d';
}
$num = intval($this->_total);
echo sprintf(isset($args[$num]) ? $args[$num] : array_pop($args), $num);
}
/**
* 执行函数
*
* @access public
* @return void
*/
public function execute() {
if (!$this->parameter->parentId) { return; }
$commentsAuthor = Typecho_Cookie::get('__typecho_remember_author');
$commentsMail = Typecho_Cookie::get('__typecho_remember_mail');
// 对已登录用户显示待审核评论,方便前台管理
if ($this->user->hasLogin()) {
$select = $this->select()->where('table.comments.cid = ?', $this->parameter->parentId)
->where('table.comments.status = ? OR table.comments.status = ?', 'approved', 'waiting');
} else {
$select = $this->select()->where('table.comments.cid = ?', $this->parameter->parentId)
->where('table.comments.status = ? OR (table.comments.author = ? AND table.comments.mail = ? AND table.comments.status = ?)', 'approved', $commentsAuthor, $commentsMail, 'waiting');
}
$threadedSelect = NULL;
if ($this->options->commentsShowCommentOnly) {
$select->where('table.comments.type = ?', 'comment');
}
$select->order('table.comments.coid', 'ASC');
$this->db->fetchAll($select, array($this, 'push'));
/** 需要输出的评论列表 */
$outputComments = array();
/** 如果开启评论回复 */
if ($this->options->commentsThreaded) {
foreach ($this->stack as $coid => &$comment) {
/** 取出父节点 */
$parent = $comment['parent'];
/** 如果存在父节点 */
if (0 != $parent && isset($this->stack[$parent])) {
/** 如果当前节点深度大于最大深度, 则将其挂接在父节点上 */
if ($comment['levels'] >= 2) {
$comment['levels'] = $this->stack[$parent]['levels'];
$parent = $this->stack[$parent]['parent']; // 上上层节点
$comment['parent'] = $parent;
}
/** 计算子节点顺序 */
$comment['order'] = isset($this->_threadedComments[$parent])
? count($this->_threadedComments[$parent]) + 1 : 1;
/** 如果是子节点 */
$this->_threadedComments[$parent][$coid] = $comment;
} else {
$outputComments[$coid] = $comment;
}
}
$this->stack = $outputComments;
}
/** 评论排序 */
if ('DESC' == $this->options->commentsOrder) {
$this->stack = array_reverse($this->stack, true);
}
/** 评论总数 */
$this->_total = count($this->stack);
/** 对评论进行分页 */
if ($this->options->commentsPageBreak) {
if ('last' == $this->options->commentsPageDisplay && !$this->parameter->commentPage) {
$this->_currentPage = ceil($this->_total / $this->options->commentsPageSize);
} else {
$this->_currentPage = $this->parameter->commentPage ? $this->parameter->commentPage : 1;
}
/** 截取评论 */
$this->stack = array_slice($this->stack,
($this->_currentPage - 1) * $this->options->commentsPageSize, $this->options->commentsPageSize);
/** 评论置位 */
$this->row = current($this->stack);
$this->length = count($this->stack);
}
reset($this->stack);
}
/**
* 将每行的值压入堆栈
*
* @access public
* @param array $value 每行的值
* @return array
*/
public function push(array $value) {
$value = $this->filter($value);
/** 计算深度 */
if (0 != $value['parent'] && isset($this->stack[$value['parent']]['levels'])) {
$value['levels'] = $this->stack[$value['parent']]['levels'] + 1;
} else {
$value['levels'] = 0;
}
$value['realParent'] = $value['parent'];
/** 重载push函数,使用coid作为数组键值,便于索引 */
$this->stack[$value['coid']] = $value;
$this->_commentAuthors[$value['coid']] = $value['author'];
$this->length ++;
return $value;
}
/**
* 输出分页
*
* @access public
* @param string $prev 上一页文字
* @param string $next 下一页文字
* @param int $splitPage 分割范围
* @param string $splitWord 分割字符
* @param string $template 展现配置信息
* @return void
*/
public function pageNav($prev = '«', $next = '»', $splitPage = 3, $splitWord = '...', $template = '') {
if ($this->options->commentsPageBreak && $this->_total > $this->options->commentsPageSize) {
$default = array(
'wrapTag' => 'ol',
'wrapClass' => 'page-navigator'
);
if (is_string($template)) {
parse_str($template, $config);
} else {
$config = $template;
}
$template = array_merge($default, $config);
$pageRow = $this->parameter->parentContent;
$pageRow['permalink'] = $pageRow['pathinfo'];
$query = Typecho_Router::url('comment_page', $pageRow, $this->options->index);
/** 使用盒状分页 */
$nav = new Typecho_Widget_Helper_PageNavigator_Box($this->_total,
$this->_currentPage, $this->options->commentsPageSize, $query);
$nav->setPageHolder('commentPage');
$nav->setAnchor('comments');
echo '<' . $template['wrapTag'] . (empty($template['wrapClass'])
? '' : ' class="' . $template['wrapClass'] . '"') . '>';
$nav->render($prev, $next, $splitPage, $splitWord, $template);
echo '</' . $template['wrapTag'] . '>';
}
}
/**
* 递归输出评论
*
* @access protected
* @return void
*/
public function threadedComments() {
$children = $this->children;
if ($children) {
//缓存变量便于还原
$tmp = $this->row;
$this->sequence ++;
//在子评论之前输出
echo $this->_singleCommentOptions->before;
foreach ($children as $child) {
$this->row = $child;
$this->threadedCommentsCallback();
$this->row = $tmp;
}
//在子评论之后输出
echo $this->_singleCommentOptions->after;
$this->sequence --;
}
}
/**
* 列出评论
*
* @access private
* @param mixed $singleCommentOptions 单个评论自定义选项
* @return void
*/
public function listComments($singleCommentOptions = NULL) {
//初始化一些变量
$this->_singleCommentOptions = Typecho_Config::factory($singleCommentOptions);
$this->_singleCommentOptions->setDefault(array(
'before' => '<ol class="comment-list">',
'after' => '</ol>',
'beforeAuthor' => '',
'afterAuthor' => '',
'beforeDate' => '',
'afterDate' => '',
'dateFormat' => $this->options->commentDateFormat,
'replyWord' => _t('回复'),
'commentStatus' => _t('您的评论正等待审核!'),
'avatarSize' => 32,
'defaultAvatar' => NULL
));
$this->pluginHandle()->trigger($plugged)->listComments($this->_singleCommentOptions, $this);
if (!$plugged) {
if ($this->have()) {
echo $this->_singleCommentOptions->before;
while ($this->next()) {
$this->threadedCommentsCallback();
}
echo $this->_singleCommentOptions->after;
}
}
}
/**
* 重载alt函数,以适应多级评论
*
* @access public
* @return void
*/
public function alt() {
$args = func_get_args();
$num = func_num_args();
$sequence = $this->levels <= 0 ? $this->sequence : $this->order;
$split = $sequence % $num;
echo $args[(0 == $split ? $num : $split) -1];
}
/**
* 根据深度余数输出
*
* @access public
* @param string $param 需要输出的值
* @return void
*/
public function levelsAlt() {
$args = func_get_args();
$num = func_num_args();
$split = $this->levels % $num;
echo $args[(0 == $split ? $num : $split) -1];
}
/**
* 评论回复链接
*
* @access public
* @param string $word 回复链接文字
* @return void
*/
public function reply($word = '') {
if ($this->options->commentsThreaded && $this->parameter->allowComment) {
$word = empty($word) ? _t('回复') : $word;
$this->pluginHandle()->trigger($plugged)->reply($word, $this);
if (!$plugged) {
echo '<a href="' . substr($this->permalink, 0, - strlen($this->theId) - 1) . '?replyTo=' . $this->coid .
'#' . $this->parameter->respondId . '" rel="nofollow" onclick="return TypechoComment.reply(\'' .
$this->theId . '\', ' . $this->coid . ');" no-pjax>' . $word . '</a>';
}
}
}
/**
* 取消评论回复链接
*
* @access public
* @param string $word 取消回复链接文字
* @return void
*/
public function cancelReply($word = '') {
if ($this->options->commentsThreaded) {
$word = empty($word) ? _t('取消回复') : $word;
$this->pluginHandle()->trigger($plugged)->cancelReply($word, $this);
if (!$plugged) {
$replyId = $this->request->filter('int')->replyTo;
echo '<a id="cancel-comment-reply-link" href="' . $this->parameter->parentContent['permalink'] . '#' . $this->parameter->respondId .
'" rel="nofollow"' . ($replyId ? '' : ' style="display:none"') . ' onclick="return TypechoComment.cancelReply();">' . $word . '</a>';
}
}
}
}
之后就是要对 comments.php
文件进行改造了,除了表单部分不变,把其他的都删掉,在文件开头这样写。
//这个 Miracle_Comments_Archive 是你刚才新建的那个类名
$this->widget('Miracle_Comments_Archive', array(
'parentId' => $this->hidden ? 0 : $this->cid,
'parentContent' => $this->row,
'respondId' => $this->respondId,
'commentPage' => $this->request->filter('int')->commentPage,
'allowComment' => $this->allow('comment')
))->to($comments);
之后就是常规的,判断是否允许评论、评论表单、评论分页之类的东西,在这里就不讨论了,不同的是,在用 $comments->listComments()
输出评论列表的时候,可以把它放在评论表单后面了。
Ajax 提交评论
搭配 ajax 能够在不刷新页面的情况下就提交评论,有助于提升用户体验。有许多大佬都写过类似的教程,只不过修改了评论嵌套方式之后情况稍微特殊了一点,虽然也只是在插入新评论的时候要稍微变通一下,但我还是打算把整个过程都写一遍。
绑定「回复」「取消回复」事件
例子:
function bindButton() {
$(".comments-reply a").click(function () {
replyTo = $(this).parent().parent().parent().parent().parent().attr("id");
console.log(replyTo);
});
$(".cancel-comment-reply a").click(function () { replyTo = ''; });
}
bindButton();
这里的选择器需要根据主题情况进行修改,这里的 .comments-reply a
和 .cancel-comment-reply a
指的分别是「回复评论」的 a 标签和「取消回复」的 a 标签。
另外,获取 replyTo
(回复评论的 id)时有.parent().parent().parent().parent().parent().attr("id")
这样的结构,是在通过这个被点击的.comments-reply a
获取到被回复评论的 id,需要根据主题实际情况修改
还要注意的是,之所以我在写这一段代码的时候要把它封装成函数,是为了兼容 pjax,在 pjax 完成页面切换之后需要在回调函数里再次调用这个函数,重新绑定按钮,避免切换页面后 ajax 评论失效。
$(document).pjax('a[href^="'+siteurl+'"]:not(...)', {container: '#pjax-container', fragment: '#pjax-container', timeout: 8000}
).on('pjax:send', function() {
beforePjax();
}).on('pjax:complete', function() {
//...
bindButton();
//...
});
before()
& after()
先定义两个函数,分别是评论提交前后执行的代码,需要注意的是,这个 before 指的是 ajax 发送但还没有得到回复的时候,after 指的是完成了评论提交、插入、处理等所有的操作。这里再给 after()
传入一个叫做 ok
的参数,用来作为评论是否发送成功的标志。
var comment = {}
comment.before = function(){
//先禁用评论表单
$("#comment-form input,#comment-form textarea").attr('disabled', true).css('cursor', 'not-allowed');
//... 这之后可以写入一些动画之类的
}
comment.after = function(ok){
//先取消对表单的禁用
$("#comment-form input,#comment-form textarea").attr('disabled', false).css('cursor', 'pointer');
if(ok){
//如果发送成功
$("#textarea").val('');//清空评论框
replyTo = '';//清空回复 id
}
//...
}
监听评论表单 submit 事件(核心)
这里是最核心的部分,大概是这样:
function commentCore() {
$('#comment-form').submit(function() {
//监听评论表单submit事件
var commentData = $(this).serializeArray(); //获取表单POST的数据
$.ajax({
type: $(this).attr('method'),
url: $(this).attr('action'),
data: commentData,
error: function(e) {
//发送 ajax 失败的处理
},
success: function(data) {
//发送成功的处理
});
}
commentCore();
error
这一部分比较简单,不是很重要,发送一个提示框告诉用户评论失败就好,这里就不赘述了。
最主要的就是 success
的部分,这个 success 不代表评论发送成功,而是 ajax 请求发送成功,所以这里还是要考虑到 Typecho 反垃圾、信息提交不完整等情况导致的评论失败。
if (!$('#comments', data).length) {//通过传过来的 data 是否包含评论区 html 判断是否成功
var msg = $('title').eq(0).text().trim().toLowerCase() === 'error' ? $('.container', data).eq(0).text() : '评论提交失败!';//获取评论失败的提示信息
notyf.error(msg);//提示用户失败,notyf.error 可以改成 alert 之类其他的方法,这个方法不是原生的
comment.after(false);//评论结束,传入 false 表示评论失败
return false
}
接下来就是对成功发送后的处理了,我们先需要获取新评论的 id
//把传来的数据压入一个新建的 body 标签里,作为一个 dom 元素储存在变量里
var htmlData = $(document.createElement('body')).append(data);
if (htmlData.html()) {
//如果 htmlData 存在,获取 id
newCommentId = htmlData.html().match(/id=\"?comment-\d+/g).join().match(/\d+/g).sort(function (a, b) { return a - b }).pop();
}else{
//如果不存在,提示错误
notyf.error('获取评论 ID 时发生错误,请尝试刷新');
return false;
}
然后,通过 replyTo 变量是否有内容判断这个评论是父级评论还是子级评论
var newComment; //先定义一个全局变量,等会用来储存新评论的 html 结构
if(''===replyTo) {
//处理父级评论
}
else {
//处理子级评论
}
处理父级评论有三种情况:
- 没有评论列表结构(没有评论)
- 不在评论列表第一页
- 在第一页,且有评论列表结构
实现代码如下:
if(!$('.comments-list').length) {
//如果没有评论列表的结构
//简单粗暴,从返回的数据中找到评论列表的结构,这里需要根据主题的实际情况修改选择器
//.comment-box 是评论表单的容器
//.comment-list-body 是评论列表的容器
$('.comment-box').after($(htmlData)[0].querySelector('.comment-list-body'));
}
else if($('.prev').length) {
//如果不在第一页,直接模拟点击分页导航的第一个 a 标签,跳转到第一页
//这里不用压入最新评论,因为跳转之后通过 pjax 或刷新就能得到最新的评论列表
$('.comments-pagenav li a').eq(1).click();
}else{
//如果在第一页且拥有评论列表的基础结构
//获取新评论的 html
newComment = $("#comment-" + newCommentId, data);
//然后将新评论压入评论列表
$('.comments-list').first().prepend(newComment);
}
然后处理子级评论,这里就有些不同了,总共分四种情况
如果回复的对象是父级评论
- 如果父级评论已有评论列表结构(.comment-children)
- 如果父级评论没有评论列表结构
- 如果回复的对象是子级评论(对应的父级评论也有评论列表结构)
实际代码如下:
newComment = $("#comment-" + newCommentId, data);
if($('#' + replyTo).hasClass('comment-parent')){
//如果回复的对象是父级评论
if ($('#' + replyTo).find('.comment-children').length) {
//当前父评论已经有嵌套的结构
//向对应的评论列表插入新评论
$('#' + replyTo + ' .comment-children .comment-list').first().prepend(newComment);
TypechoComment.cancelReply();
}
else {
//当前父评论没有嵌套的结构
//先插入嵌套的评论列表结构(根据主题实际情况)
$('#' + replyTo).append('<div class="comment-children"><div class="comments-list"></div></div>');
//插入新评论
$('#' + replyTo + ' .comment-children .comments-list').first().prepend(newComment);
TypechoComment.cancelReply();
}
}else{
//如果回复的对象是子级评论
//直接插入在对应的子级评论之后
$('#' + replyTo).after(newComment);
}
兼容评论反垃圾
Typecho 自带有反垃圾评论的功能(后他-设置-评论-反垃圾),但这不兼容 ajax 评论提交,于是很多人会选择强制关闭反垃圾,但这又可能会造成垃圾评论满天飞的奇观。这里可以用一段 js 代码来兼容 Typecho 的评论反垃圾,中间会用到一些变量,所以建议写在 php 文件里。
找到刚才的 Comments.php
在 Class 的最后加入这一段代码。
/**
* 评论反垃圾
*
* @access public
*/
public static function AntiSpam($comment) {
echo '<!--<nocompress>--><script>(function(){var a=document.addEventListener?{add:"addEventListener",focus:"focus",load:"DOMContentLoaded"}:{add:"attachEvent",focus:"onfocus",load:"onload"};var c,d,e,f,b=document.getElementById("'.$comment->respondId.'");null!=b&&(c=b.getElementsByTagName("form"),c.length>0&&(d=c[0],e=d.getElementsByTagName("textarea")[0],f=!1,null!=e&&"text"==e.name&&e[a.add](a.focus,function(){if(!f){var a=document.createElement("input");a.type="hidden",a.name="_",d.appendChild(a),f=!0,a.value='.Typecho_Common::shuffleScriptVar($comment->security->getToken($comment->request->getRequestUrl())).'}})))})();</script><!--</nocompress>-->';
}
然后回到 comments.php
,在评论表单的最后插入
<?php if($this->options->commentsAntiSpam) Miracle_Comments_Archive::AntiSpam($this); ?>
//这个 Miracle_Comments_Archive 改成你的类名
解决评论框不跟随
按下回复按钮的时候,评论框通常会直接移动到对应的评论的,如果没有,可能是少了一段神奇的 js。
function replyScript($archive) {
if ($archive->allow('comment')) echo "<!--<nocompress>--><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('$archive->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('$archive->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><!--</nocompress>-->";
}
在评论区的最开始插入:
<?php replyScript(); ?>
最后
对 Typecho 评论的改造就到这里了,这里全都是在主题层面上进行加工,希望能对你有所帮助。
Miracle! 付费版正在内测,目前演示站已经出来了,第一个测试版也已经完工了。如果想要加入内测可以联系我(QQ: 1415757672),内测版价格 40 元(早些时候还没有成型,所以算的是 30,现在进入内测是 40),进入内测可以先拿到测试版,能看到主题开发的进展。当然如果不放心也可以等正式版,价格也不会差太多。
参考
- VOID 主题 | AlanDecode
- Castle 主题 | ohmyga
- Aria 主题 | Siphils
共 2 条评论
嘿嘿~
嘿嘿