正式使用 Typecho 已经一年多, 但就目前来说, 我依旧不能很好的驾驭它. 这跟 Typecho 简约的设计理念不无关系: 它只保留了博客的基本功能. 用 Markdown 替代了传统的文本编辑器也是它们的一大特色. 但自带的 Markdown解析器 并不是特别方便, 不支持表格/ 删除线等常用排版对我的写作对我造成了一定影响. 本想搜索相关插件安装, 无奈 Typecho 的相关主题插件数量十分有限. 于是我开始怀念 Wordpress 那强大的编辑器和丰富的插件. 但回想起那运行速度和垃圾评论...

既然我作为一个 "爱折腾" 而且 "会写程序" 的人, 为啥不能 "自己动手, 丰衣足食" , 自己写一个插件来让 Typecho 支持表格等常用排版呢?

不瞒大家说, 我 PHP 水平特别水, 只是知道基本写法, 对 MVC单入口 啥的是完全不懂, 甚至不知道面向对象是个啥. 以我目前的水平, 靠自己实现一个多功能的文本编辑器是不太现实的. 我在网上搜索发现了许多优秀的开源文本编辑器, 但如果用了这些编辑器的话, 就破坏了 Typecho 简洁的风格. 于是我目光瞄向了 Markdown解析器 , 除了 Typecho 默认的 CommonMark, 还有 parsedown, HyperDown, php-markdown. 如果只是用插件实现引用外部的 Markdown 解析, 写起来超级简单啊. 既可以保持 Typecho 简约设计, 又可以支持稍微复杂的语义.

既然一窍不通, 最好的方法就是照别人的抄. 先查开发手册吧, 还好官方的开发手册质量蛮高, 查阅起来非常方便. 文档给出了一个 Hello world插件示例 , 得知了插件必须包含的函数/ 需要继承的接口/ 注释的规范, 照猫画虎搭个架子:

<?php
if (!defined('__TYPECHO_ROOT_DIR__')) exit;
/**
 * 更换为第三方 Markdown 解析引擎.
 * 
 * @package TypechoMarkDown
 * @author 8023
 * @version 16.10.06
 * @link https://8023.Moe
 */
class TypechoMarkDown_Plugin implements Typecho_Plugin_Interface
{
    public static function activate(){} //激活
    public static function deactivate(){} //禁用
    public static function config(Typecho_Widget_Helper_Form $form){} //配置
    public static function personalConfig(Typecho_Widget_Helper_Form $form){} //用户配置
    public static function render(){} //实现
}

因为插件太简单, 禁用插件的方法和用户配置的方法可以不用写. 如此来说只需要写三个函数就可以了...

首先写激活函数, 查一查插件接口列表, 发现有两个地方接口名为 Markdown , 分别为 Widget_Abstract_ContentsWidget_Abstract_Comments , 参数为 text . 如果我猜的没错, 只需要激活这俩接口, 就可以获得文章内容, 进而实现外部 Markdown 解析. 照着官方 Hello world插件示例 , 激活插件函数大概就写成了这个样子:

public static function activate() {
    Typecho_Plugin::factory('Widget_Abstract_Contents')->markdown = array('TypechoMarkDown_Plugin', 'render');
    Typecho_Plugin::factory('Widget_Abstract_Comments')->markdown = array('TypechoMarkDown_Plugin', 'render');
}

其次就是配置页面了, 我初步想法是加入尽可能多的 Markdown解析器 供用户选择. 所以我吧 parsedown & HyperDown & php-markdown 的源码全部下载了下来. 同样, 为了确定用户选择了哪一种解析器, 需要写一组单选框. 在官方示例Typecho插件教程二中我们得知了类名的命名规则, Typecho_Widget_Helper_Form_Element_Text 类所对应的文件就应该是 var/Typecho/Widget/Helper/Form/Element/Text.php , 同级目录下的文件分别是 Checkbox/ Fake/ Hidden/ Password/ Radio/ Select/ Submit/ Text/ Textarea , 那么单选框的类名不出意外就应该是 Typecho_Widget_Helper_Form_Element_Radio , 看其基类 Typecho_Widget_Helper_Form_Element 的构造函数, 参数分别是名称/ 选项/ 默认值/ 标题/ 描述. 实例化后别忘用 $form->addInput(); 把定义的变量写入到配置项. 配置函数差不多就写成这个样子:

public static function config(Typecho_Widget_Helper_Form $form) {
    /** 解析引擎选择 */
    $engine = new Typecho_Widget_Helper_Form_Element_Radio('engine',
        array(
               'parsedown' => '<a href="https://github.com/erusev/parsedown" target="_blank">parsedown</a>',
               'HyperDown' => '<a href="https://github.com/SegmentFault/HyperDown" target="_blank">HyperDown</a>',
            'php-markdown' => '<a href="https://github.com/michelf/php-markdown" target="_blank">php-markdown</a>',
        ),'parsedown', _t('选择 Markdown 解析引擎'), _t('请先确保文章和评论 Markdown 解析功能已经被正确启用.<br />Typecho默认的解析引擎为<a href="https://github.com/thephpleague/commonmark" target="_blank">CommonMark</a>, 路径/var/CommonMark.<br />本插件所依赖的解析引擎位于/usr/plugins/TypechoMarkDown/, 可自行去Github下载最新版替换升级.'));
    $form->addInput($engine);
    /** 超链接打开方式选择 */
    $newtab = new Typecho_Widget_Helper_Form_Element_Radio('newtab', 
        array(
            'notmind' => '不介意',
               'true' => '是',
              'false' => '否',
        ), 'true', _t('使用新标签页打开文内链接'), _t('想让所有链接转换为使用新标签页打开请选择是, 否则选否.<br />不介意指以Markdown解析器输出为准.'));
    $form->addInput($newtab);
}

最后是实现方法, 非常简单的 switch case 语句, 通过之前配置面板所选择的选项加载不同的 Markdown解析器 , 每个库的使用方法在其 Github 上有讲, 直接抄下来稍加改动即可. 至于把所有的链接改变为在新标签页打开, 只需要进行一次正则替换即可.

public static function render($text) {
    switch (Typecho_Widget::widget('Widget_Options')->plugin('TypechoMarkDown')->engine) {
        case 'HyperDown':
            include_once dirname(__FILE__) . '/HyperDown/Parser.php';
            $parser = new HyperDown\Parser();
            $html = $parser->makeHtml($text);
            break;
        case 'parsedown':
            include_once dirname(__FILE__) . '/parsedown/Parsedown.php';
            $Parsedown = new Parsedown();
            // $Parsedown->setMarkupEscaped(true); //转义字符串
            // $Parsedown->setUrlsLinked(false); //不把url转换为链接
            // $Parsedown->setBreaksEnabled(true); //匹配换行为<br />, 否则只匹配段落
            $html = $Parsedown->setBreaksEnabled(true)->text($text);
            break;
        case 'php-markdown':
            include_once dirname(__FILE__) . '/php-markdown/Michelf/MarkdownExtra.inc.php';
            $parser = new Michelf\MarkdownExtra();
            $parser->hard_wrap = true;
            $html = $parser->transform($text);
            break;
        default :
            $html = $text;
    }
    switch (Typecho_Widget::widget('Widget_Options')->plugin('TypechoMarkDown')->newtab) {
        case 'true':
            $html = preg_replace('/<(a.*?)>/', '<$1 target="_blank">', $html);
            break;
        case 'false':
            // $html = preg_replace('', '', $html);
            break;
        default:
            break;
    }
    return $html;
}

如此, 插件就算写完了, 把文件命名为 Plugin.php 上传到 8023.moe/usr/plugins/TypechoMarkDown/ , 控制台打开插件管理, 看到插件已经显示, 启用插件打开插件配置面板, 设置完成后点击保存.
插件管理.png
插件设置.png

经过测试, 插件正常运行, 不仅可以嵌入表格/ 文字删除线/ 超链接新标签页打开这些原版 Markdown解析器 所不支持的功能, 还保证了 Typecho 的简洁清新. 当然, 这个插件肯定没有那么完美, 比如不支持LaTeX和简单的流程图 说好的简洁呢 , 甚至可能造成XSS漏洞... 不过我以后如果发现了什么问题或有了什么好想法也会加入进来. 如果大家觉得这个插件不错, 可以在 Github关注这个项目, 当然也欢迎大家留言评论或 提交 Issues .

写完这篇文章和插件之后, 我忽然意识到到... Typecho 是支持直接写HTML的... 也就是说, 我只需要用自己喜欢的 Markdown编辑器 写完文章后直接复制HTML至我的博客即可_(:3」∠)_

顺便一提, 最新版本的 Typecho 17.10.30 已经原生支持上述格式, 且可以使用前后各三个叹号包裹 HTML 代码的方式来进行不转义地插入自定义代码. 故不再建议大家使用这篇文章所述的方式改动自己的 Typecho.