最近深深的迷上了Markdown,无论是编写代码说明文档还是记日记写博客,都能用Markdown快速轻松的书写。使用过GitHub的人应该都知道Markdown,Markdown让人沉浸在写作之中,而不用关心排版问题,只需几个简单的标记就能完成文档的自动排版。而且由于它是纯文本,具有极强的兼容性,可以方便的转换为各种格式。

于是我开始寻找WordPress上支持Markdown的编辑器,虽然有一些但都不是很好。便又开始寻找支持Markdown的博客系统,在这个开源遍地开花的年代,这样的博客系统还真是多如牛毛,让人眼花缭乱。有系统庞大功能性非常复杂的,也有小巧轻盈扩展性非常完美的。在这么多的博客系统中,最终我选择了Typecho,不仅是因为Typecho的轻量高效稳定简洁的特性,而且这款博客系统完全是由国人团队开发,选择使用Typecho也算是对国内的开源事业做出的一点点支持吧。

Typecho仅仅使用了7张数据表和不足400KB的代码,就实现了完整的插件与模板机制,打造了一个和WordPress几无二致的博客系统,而且Typecho和WordPress兼容,很容易从WordPress转到Typecho。不过,虽说如此,但是我在安装Typecho以及转换的过程中还是遇到了不少的问题和挫折,在鼓捣了一周左右的时间后,终于大功告成,仅以此文纪念之。

一、安装Typecho

Typecho的安装非常方便,按照官方的安装步骤,首先从这里下载最新的稳定版本(写这篇博客时,最新版本为1.0),然后解压并上传到服务器Web目录,然后使用浏览器打开你的博客地址,根据Typecho安装程序的提示一步步的安装即可。

当然这是理想情况下的安装步骤,实际上我的安装过程并没有这么顺利,在填完我的配置信息提交之后,出现了下面的错误:

安装程序捕捉到以下错误:"". 程序被终止, 请检查您的配置信息.

install_fail.png

对的你没看错,以下错误是什么错误?是个空字符串!不得不说这真的是一个非常坑爹的错误提示。幸亏Typecho是个开源的博客程序,在install.php文件中搜索字符串“安装程序捕捉到以下错误”,于是便找到了这个异常代码的所在之处(无关代码已省略):

try {
    $installDb = new Typecho_Db($config['adapter'], $config['prefix']);
    $installDb->addServer($config, Typecho_Db::READ | Typecho_Db::WRITE);

    /** 初始化数据库结构 */
    // ...

    $scripts = explode(';', $scripts);
    foreach ($scripts as $script) {
        $script = trim($script);
        if ($script) {
            $installDb->query($script, Typecho_Db::WRITE);  // 这里抛出异常
        }
    }
} catch (Typecho_Db_Exception $e) {
    $success = false;
    $code = $e->getCode();

    if (...) {
        // ...
    } else {
        echo '<p class="message error">' 
         . _t('安装程序捕捉到以下错误: "%s". 程序被终止, 请检查您的配置信息.',$e->getMessage()) . '</p>';
    }
}

抛出异常的正是上面注释的那句$installDb->query($script, Typecho_Db::WRITE),而$installDb是这样定义的:

$installDb = new Typecho_Db($config['adapter'], $config['prefix']);

Typecho_Db类根据用户选择的数据库适配器进行数据库操作,安装时配置页有两个可供选择:Mysql原生的和Pdo驱动的,如下图的下拉列表所示:

db_config.png

而在这里我选择了默认的Mysql原生函数适配器,对应的数据库处理代码在/var/Typecho/Db/Adapter/Mysql.php文件中,看到下面的代码我瞬间明白了为什么会有这么奇怪的错误提示了:

    /**
     * 数据库连接函数
     *
     * @param Typecho_Config $config 数据库配置
     * @throws Typecho_Db_Exception
     * @return resource
     */
    public function connect(Typecho_Config $config)
    {
        if ($this->_dbLink = @mysql_connect($config->host . (empty($config->port) ? '' : ':' . $config->port),
        $config->user, $config->password, true)) {
            if (@mysql_select_db($config->database, $this->_dbLink)) {
                if ($config->charset) {
                    mysql_query("SET NAMES '{$config->charset}'", $this->_dbLink);
                }
                return $this->_dbLink;
            }
        }

        /** 数据库异常 */
        throw new Typecho_Db_Adapter_Exception(@mysql_error($this->_dbLink));
    }

原因就在于上面代码中的@mysql_connect@mysql_select_db@符号是php中特有的一种错误控制符(error-control operator),意味着如果这个函数抛出异常将被完全忽略掉,没有errorCode,没有errorMessage。所以上面的错误提示信息使用$e->getMessage()方法获取的是个空字符串。

安装失败的原因现在已经定位到了是数据库导致的,接下来就看看为什么数据库配置会失败。这时候我注意到上面图片中红色高亮的提示信息:

系统将为您自动匹配 SAE 环境的安装选项

其实在安装的过程中我就很纳闷,为什么都没有提示我输入数据库的用户名密码这些配置信息,原来是Typecho检测到了我的系统为SAE环境,直接使用SAE里的数据库配置。而我的环境确实是SAE不假,只不过我使用的是SAE的本地开发环境,而且我从来没有做过SAE的数据库配置。按F12查看页面源码发现数据库的配置确实都是空。

sae_cfg.png

于是打开SAE的配置文件sae.conf

DocumentRoot path/to/website

http_port 80
https_port 443
redis_port 6379
domain sinaapp

mysql_user 
mysql_pass 
mysql_host 
mysql_port 

proxy_host  
proxy_port 80
proxy_username 
proxy_password 

open_xdebug 0
autoupgrade 1

其中mysql_user, mysql_pass, mysql_host, mysql_port分别对应数据库的用户名、密码、主机地址和端口号。于是马上填好这些配置参数,重启SAE,重新安装Typecho,这时这些参数都OK了,但还是差了一个参数dbDatabase,这个值一直是app_:

<input type="hidden" name="dbDatabase" value="app_">

这个app_其实是SAE_MYSQL_DB的值,于是我们查看SAE的代码,在文件sae/emulation/loadsae.php中找到了它的定义:

define('SAE_MYSQL_DB', 'app_'.$_SERVER['HTTP_APPNAME']);

原来app_是数据库的前缀,后面还有一个叫做HTTP_APPNAME$_SERVER变量。从变量名可以推断出这应该是SAE应用的名称,所以可能还需要在SAE中创建应用,但是具体操作我就没深究了,而是采用了一个取巧的方法,直接修改下面的这两行HTML源码:

<input type="hidden" name="config" value="array (
    'host'      =>  SAE_MYSQL_HOST_M,
    'user'      =>  SAE_MYSQL_USER,
    'password'  =>  SAE_MYSQL_PASS,
    'charset'   =>  'utf8',
    'port'      =>  SAE_MYSQL_PORT,
    'database'  =>  'typecho_db'
)">
<input type="hidden" name="dbDatabase" value="typecho_db">

然后点击“确认”按钮提交,终于看到了安装成功的页面!

二、将WordPress数据导出到Typecho

接下来,我便马不停蹄的开始了我的Typecho体验之旅。首先第一件事便是将之前WordPress上的博文导到Typecho上来,好消息是Typecho官方已经提供这样的插件了,我们去这里下载wordpresstotypecho然后遵循步骤解压上传到插件目录并在后台的插件管理里启动该插件。(虽然官网上有提示信息说:仅适用于wordpress2.7,但在我的4.2版本下也转换成功了)

就在我兴高采烈的去后台的插件管理里点击“启动插件”链接满心期待着我的WordPress华丽变身Typecho时,一个白底的页面并伴随着一个Fatal error吓到了我:

Fatal error: Call to undefined function mb_regex_encoding() in typechovarTypechoCommon.php on line 810

WTF!一百只草泥马从眼前飞过有木有!哎,没办法,继续看代码看看哪里出错了吧,谁叫我们是程序猿呢。搜索函数mb_regex_encoding,还好,只有Common.php中用到了一次,代码如下:

/**
 * 生成缩略名
 */
public static function slugName($str, $default = NULL, $maxLength = 128)
{
    // ...
    
    if (__TYPECHO_MB_SUPPORTED__) {
        mb_regex_encoding(self::$charset);
        mb_ereg_search_init($str, "[\w" . preg_quote('_-') . "]+");
        $result = mb_ereg_search();
        // ...
    } else if ('UTF-8' == strtoupper(self::$charset)) {
        // ...
    } else {
        // ...
    }

    $str = trim($str, '-_');
    $str = !strlen($str) ? $default : $str;
    return substr($str, 0, $maxLength);
}

这段代码的用途是根据字符串自动生成缩略名(slugName),其中使用到的mb_打头的函数都是php提供的针对多字节字符串处理的函数。但是mb_函数并不是所有的php版本都支持,所以在使用时需要先确定系统是否支持,Typecho通过下面的方法来判断系统是否支持mb_函数的:

define('__TYPECHO_MB_SUPPORTED__', function_exists('mb_get_info'));

可见,Typecho认为只要系统支持mb_get_info函数,应该也支持mb_regex_encoding这些mb_函数。而事实却并不是这样,我尝试了几个不同版本的Windows上的PHP,发现都是支持mb_get_info而不支持mb_regex_encoding,我不知道是不是Windows版本的PHP都会存在这个问题,Google之并没有得到我需要的答案,有人说可能是权限问题,有人说是配置问题,还有人说需要使用--enable-mbstring参数重新编译下PHP。哦,在Windows下重新编译PHP?实在是太酷了!但是,等一下,我好像只是想装个博客系统而已。

让我们回到现实,不要走的太远。slugName这个函数也不是什么核心功能,而且我们看代码可以知道,如果不支持mb_函数后面照样能走通,既然如此,我们暂且就认为我们的系统不支持mb_函数好了,这又能怎么样呢?于是稍微改了下代码:

define('__TYPECHO_MB_SUPPORTED__', function_exists('mb_regex_encoding'));

再点“启用插件”链接,OK,顺利进来了。按照页面提示,需要填写WordPress的数据库配置信息,然后进入“从Wordpress导入数据”页面,点击“开始数据转换”按钮。轻松几秒,转换完成,去“管理文章”页面看下,是不是非常熟悉的文章列表页面又回来了?

三、折腾Markdown

好了,现在博客已经搭建完毕,可以在Typecho下尽情的使用我最喜爱的Markdown语法进行写作了,这就结束了吗?哦,当然不,我们的Typecho之旅才刚刚开始!谁让我们程序猿都是爱折腾的人呢!

我们随便编辑一篇文章,Typecho会弹出下面的提示文字:

这篇文章不是由Markdown语法创建的, 继续使用Markdown编辑它吗?

选择“是”即可使用Markdown语法书写,这样文章的格式就是Html和Markdown混合格式。这多多少少有些让人感觉不爽,特别是看到类似下面的列表代码时:

<ul>
    <li>blah ...</li>
    <li>blah blah ...</li>
    <li>blah blah blah ...</li>
</ul>

对于我们这些有着“洁癖”和“强迫症”的程序猿来说,是不是就有种强烈的冲动想把它改成下面的Markdown代码:

- blah ...
- blah blah ...
- blah blah blah ...

怎么办呢?难道我们要手动把之前的所有博客都转换成Markdown语法么?Of course, No. 我们程序猿的特点就是这样:就算是能够在一个小时内手工完成的工作,也要写上六个小时的代码,然后再调试六个小时,最后花三秒钟运行自动完成!

Google上搜索 “Html to Markdown” 得到大量结果,说明这种工作已经有大把大把的人做过了,我们要做的就是找个靠谱点的工具直接用就好了。最终我把目光锁定在了html2text这个开源的Python项目上,并且打算写段Python脚本自动将我之前的所有的博客转换为Markdown语法。代码非常简单:

def main():
    
    posts = get_all_posts()
    posts = convert_posts_to_markdown(posts)
    save_posts(posts)

    return

get_all_posts函数从MySQL数据库中获取Typecho所有文章列表(也就是typecho_contents表),convert_posts_to_markdown直接调用函数html2text.html2text()将文章转换为Markdown语法,然后再调用save_posts将转换后的文章保存到数据库。

理论上这短短三行代码就能瞬间搞定,嗯,理论上。

事实上,在我的很多文章中有很多的代码块,而我为了让我的代码块看起来好看一点,使用了WordPress上提供的一个代码高亮插件:SyntaxHighlighter Evolved。它的语法类似于下面这样:

[code lang='javascript']
function sayHello() {
    console.log('hello');
}
[/code]

在使用html2text.html2text()函数转换这样的代码时,会把它视为普通的段落文本,这样我所有的代码里的空格换行全被剔除了,变成了一团乱麻。于是稍微修改了下程序的逻辑,在转换之前先用正则将文章中所有[code...]...[/code]这种格式的代码替换成唯一的uuid,然后再转换之后再将uuid替换为类似下面的代码:

function sayHello() {

console.log('hello');

}

完整的脚本可以参考这里,你可能需要根据你的插件或其他的一些不同的语法做些调整。

四、折腾主题样式

现在所有的文章都已经转换成Markdown语法了,去主页上随便逛逛,看着自己的劳动成果即将面世还是有些成就感的。接下来便是换个靠谱的主题了。

我个人是比较推崇单列布局的宽屏主题,这样不仅显得页面很干净清爽,让人看文章时能保持专注不至于被侧边栏的内容分心,而且在宽屏下代码可以完整的显示出来,如果页面很窄的话,代码过长就会导致出现大量横向的滚动条,如果设置成不显示滚动条又会导致代码出现大量的自动换行,看起来非常不舒服,完全没有在IDE中看代码的感觉。使用WordPress时我使用的就是这样的主题:Decode,而且自己做了点调整,将内容的宽度调大了一点。于是,我还要找一个类似这样的Typecho主题。

最终,我选择了Typecho论坛上一个非常火的主题:Navy,虽然没有找到这个主题比较官方的链接,但是可以直接从论坛里下载使用。Navy主题清爽简洁,唯一不足的一点是:右侧栏展开后只能显示“最新文章”和部分的“分类”,下面剩下的“分类”部分和“归档”都未显示,并且也不能用鼠标滚轮向下滚动。于是修改了下样式文件navy/style.css,对侧边栏增加了overflow-y: auto;

#secondary {
    height: 100%;
    width: 300px;
    top: 0;
    right: -330px;
    position: fixed;
    z-index: 100;
    box-shadow: -3px 0 8px rgba(0, 0, 0, 0.1);
    font-size: 13px;
    background-color: #303538;
    color: #f8f8f8;
    overflow-y: auto;  /* 右侧栏显示垂直滚动条 */
}

这样修改之后,侧边栏右边会出现一个滚动条,这个滚动条和浏览器的滚动条并行显示,虽然看起来比较ugly,但也勉强够用了。另外对Navy主题的样式还做了些其他的定制(不感兴趣的同学请直接忽略):

.container {
    width:1150px;  /* 增大内容宽度 */
    padding:0 60px;
    margin:0 auto;
}

a, button.submit {
    color:#009BCD;  /* 链接颜色改为淡蓝色 */
    text-decoration:none;
    -webkit-transition:all .1s ease-in;
    -moz-transition:all .1s ease-in;
    -o-transition:all .1s ease-in;
    transition:all .1s ease-in;
}

pre, code {
    background: #F0F0EC;
    color: #C13;  /* 代码的背景为灰色,字体为暗红色 */
    font-family:Menlo, Monaco, Consolas, "Lucida Console", "Courier New", monospace;
    font-size:.92857em;
    /*display: block;*/  /* 代码块内联显示 */
}

.post-content img {
    max-width:100%;  /* 防止图片太宽撑出去 */
    margin-left: 0;
}

blockquote,.stressed {
    -moz-box-sizing: border-box;
    box-sizing: border-box;
    margin: 1.8em 0 1.8em 2.2em;  /* 引用文字改为向内缩进,太突出感觉怪怪的 */
    padding: 0 3.8em 0 1.6em;
    border-left: #4a4a4a 0.4em solid;
    /* font-style: italic; */  /* 去掉引用文字的斜体 */
    color: #888;
}

.post-content ul {
    overflow: auto;
    /*padding: .5em 2.4em;*/
    border-radius: 3px;
    /*margin:1.8em 0;*/  /* 列表的内外边距都太大了 */
}

五、折腾代码高亮

到这里,博客基本上已经打造的差不多了,但是还差最后一步,Typecho默认的代码样式简直让人想起了上个世纪在记事本里写代码的年代。确实,在如今的开发环境中,代码高亮几乎已成为标配,没有高亮的代码无论是给人阅读,还是书写都让人感到非常不爽。

所以,最后一步,我来为我的Typecho加上代码高亮的功能,而这样的插件也直接就已经有现成的了,我这里还是使用著名的SyntaxHighlighter插件来实现代码高亮,它的Typecho版是:SyntaxHighlighter-For-Typecho。从GitHub上直接"Download ZIP",然后解压并上传到服务器上的Typecho插件目录(要特别注意的是:插件的目录名和插件类名必须保持一致,譬如这里的类名为SyntaxHighlighter_Plugin,所以目录名必须是SyntaxHighlighter,要不然会出现500 Server Error,这又是一个非常非常坑爹的错误提示!我认为Typecho在出错时的提示信息上面还可以做的更好)

六、Markdown标记小结

Typecho实现了最基本的一些Markdown语法,我这里总结下。

6.1 字体加粗

语法:**粗体**
结果:粗体

6.2 斜体

语法:*斜体*
结果:斜体

6.3 链接

语法:[百度](http://www.baidu.com)
结果:百度

6.4 图片

语法:![image text](https://www.baidu.com/img/bdlogo.png)
结果:

image text

6.5 标题

语法:

# This is <h1>
## This is <h2>
### This is <h3>

结果:

This is <h1>

This is <h2>

This is <h3>

6.6 列表

语法:

1. 有序列表One
2. 有序列表Two
3. 有序列表Three

- 无序列表One
- 无序列表Two
- 无序列表Three

结果:

  1. 有序列表One
  2. 有序列表Two
  3. 有序列表Three
  • 无序列表One
  • 无序列表Two
  • 无序列表Three

6.7 引用

语法:> 这是引用文字
结果:

这是引用文字

6.8 行内代码

语法: `var i = 0;`
结果:
var i = 0;

6.9 代码块

语法:
```
function sayHello() {
}
```
结果:

function sayHello() {
}

Typecho支持的Markdown语法有限,其实,还有很多其他的语法在特定的场景下也会比较有用,譬如绘制表格,删除线,表情,LaTeX格式,流程图,序列图等等。可以看下作业部落的Cmd Markdown编辑器,其华丽程度简直让人目瞪口呆。然后这里有一份比较规矩还不错的语法说明,另外GitHub也有Markdown的一份指导文档

参考

  1. 开始用 Markdown 写博客
  2. Typecho文档站点
  3. php - @mysql_connect and mysql_connect - Stack Overflow
  4. PHP手册 > 函数参考 > 国际化与字符编码支持 > 多字节字符串
  5. Cmd Markdown 编辑阅读器 - 作业部落出品
  6. Markdown 语法说明 (简体中文版)
  7. Mastering Markdown · GitHub Guides
扫描二维码,在手机上阅读!