Typecho 主题制作总结

由于 Typecho 相比 WordPress,所提供的接口较少,所以有些地方需要注意下。

首页文章无限加载文章

首先在 functions.php 中加入一段判断 ajax 请求的方法:

function is_ajax()
{
    if (isset($_SERVER['HTTP_X_REQUESTED_WITH'])) {
        if ('xmlhttprequest' == strtolower($_SERVER['HTTP_X_REQUESTED_WITH'])) {
            return true;
        }
    }
    return false;
}

然后在首页模板 index.php 中加入判断,if (is_ajax ()): 则不输出 header footer 等信息。
最后一步,再模板公共 js 文件中加入以下代码:

    var page = 1;
    var $body = (window.opera) ? (document.compatMode == "CSS1Compat" ? $('html') : $('body')) : $('html,body');
    $('#blog_load_more').click(function(event) {
        $(this).hide();
        $('#spinner').show();
        $.ajax({
            type: 'get',
            url: SITE.default_url + '/page/' + parseInt(page + 1),
            success: function(data, textStatus, XMLHttpRequest) {
                page++;
                $('.w-blog-list').append(data);
                $('#spinner').hide();
                $('#blog_load_more').show();
            },
            error: function(MLHttpRequest, textStatus, errorThrown) {
                $('#spinner').hide();
                $.jGrowl('Network Error');
            }
        });
    });

其中 #blog_load_more 为需要绑定的加载更多按钮,#spinner 为加载过程中的动画,.w-blog-list 为首页文章列表容器。
由于 Typecho 并没有 Ajax 钩子函数,所以需要在 functions.php 中加入以下代码:

function site_data()
{
    $array = array(
        'site_url'         => Helper::options()->siteUrl,
        'default_url'      => Helper::options()->siteUrl . 'index.php',
        'theme_images_url' => Helper::options()->themeUrl . '/assets/images/',
    );
    echo json_encode($array);
}

之后在 footer.php 中引入该数据:

<script type="text/javascript">
var SITE = <?php site_data()?>;
</script>

此处的 SITE 就是一个 JSON 对象,可以直接用了。

WordPress 的 add_filter 很好用,Typecho 也可以实现类似的功能:

比如想把文章中的图片都加上 rel="fancybox" 的属性,WordPress 可能使用的以下方法:

add_filter('the_content', 'fancybox_replace');
function fancybox_replace($content)
{
    global $post;
    $pattern     = "/<a(.*?)href=('|\")([^>]*).(bmp|gif|jpeg|jpg|png)('|\")(.*?)>(.*?)<\/a>/i";
    $replacement = '<a$1href=$2$3.$4$5 target="_blank" rel="fancybox"$6>$7</a>';
    $content     = preg_replace($pattern, $replacement, $content);
    return $content;
}

Typecho 中可以这么写:

function themeInit($archive)
{
    if ($archive->is('single')) {
        $archive->content = fancybox_replace($archive->content);
    }

}

获取文章第一张图片

分为 markdown 和 html 两种方式

function get_post_img($archive)
{
    $cid = $archive->cid;
    $db = Typecho_Db::get();
    $rs = $db->fetchRow($db->select('table.contents.text')
                               ->from('table.contents')
                               ->where('cid=?', $cid));
    $text = $rs['text'];
    if (0 === strpos($text, '')) {
        preg_match('/!\[[^\]]*]\([^\)]*\.(png|jpeg|jpg|gif|bmp)\)/i', $text, $img);
        if (empty($img)) {
            return 'none';
        } else {
            preg_match("/(?:\()(.*)(?:\))/i", $img[0], $result);
            $img_url = $result[1];
            return '<img src="' . $img_url . '"/>';
        }
    } else {
        preg_match_all("/\<img.*?src\=\"(.*?)\"[^>]*>/i", $text, $img);
        if (empty($img)) {
            return 'none';
        } else {
            $img_url = $img[1][0];
            return '<img src="' . $img_url . '"/>';
        }
    }
}

评论列表加 @

function get_comment_at($coid)
{
    $db   = Typecho_Db::get();
    $prow = $db->fetchRow($db->select('parent')->from('table.comments')
                                 ->where('coid = ? AND status = ?', $coid, 'approved'));
    $parent = $prow['parent'];
    if ($parent != "0") {
        $arow = $db->fetchRow($db->select('author')->from('table.comments')
                                     ->where('coid = ? AND status = ?', $parent, 'approved'));
        $author = $arow['author'];
        $href   = '<a href="#comment-' . $parent . '">@' . $author . '</a>';
        echo $href;
    } else {
        echo '';
    }

}

非插件实现文章阅读次数统计

在文章页面引入 get_post_view () 方法。

//get_post_view($this)
function get_post_view($archive)
{
    $cid    = $archive->cid;
    $db     = Typecho_Db::get();
    $prefix = $db->getPrefix();
    if (!array_key_exists('views', $db->fetchRow($db->select()->from('table.contents')))) {
        $db->query('ALTER TABLE `' . $prefix . 'contents` ADD `views` INT(10) DEFAULT 0;');
        echo 0;
        return;
    }
    $row = $db->fetchRow($db->select('views')->from('table.contents')->where('cid = ?', $cid));
    if ($archive->is('single')) {
       $db->query($db->update('table.contents')->rows(array('views' => (int) $row['views'] + 1))->where('cid = ?', $cid));
    }
    echo $row['views'];
}

判断移动端访问

function is_mobile()
{
    $user_agent     = $_SERVER['HTTP_USER_AGENT'];
    $mobile_browser = array(
        "mqqbrowser", //手机QQ浏览器
        "opera mobi", //手机opera
        "juc", "iuc", //uc浏览器
        "fennec", "ios", "applewebKit/420", "applewebkit/525", "applewebkit/532", "ipad", "iphone", "ipaq", "ipod",
        "iemobile", "windows ce", //windows phone
        "240x320", "480x640", "acer", "android", "anywhereyougo.com", "asus", "audio", "blackberry",
        "blazer", "coolpad", "dopod", "etouch", "hitachi", "htc", "huawei", "jbrowser", "lenovo",
        "lg", "lg-", "lge-", "lge", "mobi", "moto", "nokia", "phone", "samsung", "sony",
        "symbian", "tablet", "tianyu", "wap", "xda", "xde", "zte",
    );
    $is_mobile = false;
    foreach ($mobile_browser as $device) {
        if (stristr($user_agent, $device)) {
            $is_mobile = true;
            break;
        }
    }
    return $is_mobile;
}

ajax 评论

AjaxComments 有小 bug,不太好用,直接食用以下的修改版代码,在公共 js 中调用 ajaxComments () 方法即可

function ajaxComment() {
    var selector = {
        commentMainFrame: '#comment',
        commentList: '#commentlist',
        commentNumText: '#comment h3',
        commentReplyButton: '#comment span.reply',
        submitForm: '#commentform',
        submitTextarea: '#textarea',
        submitButton: '#submit',
    };
    var parentId = '';
    bindCommentReplyButton();
    $(selector.submitTextarea).after('<div style="display:none;" id="ajaxCommentMsg"><\/div>');
    $msg = $('#ajaxCommentMsg');
    $(document).on('submit', selector.submitForm, function() {
        $msg.empty();
        $(selector.submitButton).val('发射中哦=A=');
        $(selector.submitButton).attr('disabled', true).fadeTo('slow', 0.5);
        if ($(selector.submitForm).find('#author')[0]) {
            if ($(selector.submitForm).find('#author').val() == '') {
                message('昵称没填呢QAQ');
                enableCommentButton();
                return false;
            }
            if ($(selector.submitForm).find('#mail').val() == '') {
                message('邮箱没填呢QAQ');
                enableCommentButton();
                return false;
            }
            var filter = /^[^@\s<&>]+@([a-z0-9]+\.)+[a-z]{2,4}$/i;
            if (!filter.test($(selector.submitForm).find('#mail').val())) {
                message('邮箱地址不正确呢QAQ');
                enableCommentButton();
                return false;
            }
        }
        if ($(selector.submitForm).find(selector.submitTextarea).val() == '') {
            message('评论似乎什么也没写呢QAQ');
            enableCommentButton();
            return false;
        }
        $.ajax({
            url: $(this).attr('action'),
            type: $(this).attr('method'),
            data: $(this).serializeArray(),
            error: function() {
                message('发射失败,请重试!');
                setTimeout(NProgress.done, 500)
                enableCommentButton();
                return false;
            },
            success: function(data) {
                if (!$(selector.commentList, data).length) {
                    errorMsg = data.match(/.+/g).join().match(/\<div.+\>.+\<\/div\>/g).join().match(/[^\,]+/g);
                    $msg.html(errorMsg[0] + errorMsg[1] + errorMsg[2]);
                    enableCommentButton();
                    return false;
                } else {
                    userCommentId = $(selector.commentList, data).html().match(/id=\"?comment-\d+/g).join().match(/\d+/g).sort(function(a, b) {
                        return a - b;
                    }).pop();
                    commentLi = '<li id="comment-' + userCommentId + '" class="comment">' + $('#comment-' + userCommentId, data).html(); + '<\/li>';
                    if (parentId) {
                        if ($('#' + parentId).find(".comment-children").length <= 0) {
                            $('#' + parentId).append("<ul class='children'></ul>");
                        }
                        $('#' + parentId + " .children:first").append(commentLi);
                        parentId = ''
                        $body.animate({
                            scrollTop: $('#comment-' + userCommentId).offset().top - 450
                        }, 900);
                    } else {
                        $(selector.commentList).prepend(commentLi)
                        $body.animate({
                            scrollTop: $('#comment-' + userCommentId).offset().top - 200
                        }, 900);
                    }
                    //$('#comment-' + userCommentId).slideDown('slow');

                    //console.log(userCommentId);
                    $(selector.commentNumText).length ? (n = parseInt($(selector.commentNumText).text().match(/\d+/)), $(selector.commentNumText).html($(selector.commentNumText).html().replace(n, n + 1))) : 0;
                    TypechoComment.cancelReply();
                    $(selector.submitTextarea).val('');
                    $(selector.commentReplyButton + ' b, #cancel-comment-reply-link').unbind('click');
                    bindCommentReplyButton();
                    enableCommentButton();

                }
            }
        });
        return false;
    });

    function bindCommentReplyButton() {
        $(document).on('click', selector.commentReplyButton, function() {
            parentId = $(this).parents('li.comment').attr("id");
            $(selector.submitTextarea).focus();
        });
        $(document).on('click', '#cancel-comment-reply-link', function() {
            parentId = '';
        });
    }

    function enableCommentButton() {
        $(selector.submitButton).attr('disabled', false).fadeTo('', 1);
        $(selector.submitButton).val('发射=A=');
    }

    function message(msg) {
        $msg.hide();
        $msg.html(msg).slideToggle('fast');
    }
}

无限嵌套评论

function themeInit($archive)
{
    Helper::options()->commentsMaxNestingLevels = 999;
}

非插件实现路由

function themeInit($archive)
{
    if ($archive->is('archive', 404))
    {
        $path_info = trim($archive->request->getPathinfo(), '/');
        if ($path_info == 'i/redirect')
        {
            $url = urldecode($archive->request->url);
            $archive->response->redirect($url);
            exit;
        }
    }
}