给力星

Web Developer

扩展Markdown支持自定义样式属性

使用 Markdown 来写作是一个很好的体验,但纯 Markdown 的样式表现力有点欠缺,主要是定义样式比较困难。例如要将某一段文字的背景颜色改为蓝色,就得使用 HTML 代码:

<p class="bg-blue">弗朗茨·李斯特(Franz Liszt,1811年10月22日-1886年7月31日),著名匈牙利作曲家、钢琴家、指挥家,是**浪漫主义前期**最杰出的代表人物之一。</p>

直接使用 HTML 代码不是难事,主要问题在于,某些 Markdown 解析器不会对包含 HTML 代码的内容段进行解析。

也就是说,上面的 **浪漫主义前期** 并不会解析成 <strong>浪漫主义前期</strong>,需自行写成 HTML 代码,这样在互转方面会存在问题。PHP Markdown Extra 是通过增加一个标签属性 markdown 来解决该问题的,markdown="1" 表示 HTML 代码段中内容也要进行解析,如:

<p class="bg-blue" markdown="1">弗朗茨·李斯特(Franz Liszt,1811年10月22日-1886年7月31日),著名匈牙利作曲家、钢琴家、指挥家,是**浪漫主义前期**最杰出的代表人物之一。</p>

不过这样的方式对于添加样式来说,可能还不是很理想。通过扩展 Markdown 语法,例如 GitHub 上就有一个项目是用 !!<class name>|<text to be wrapped>!! 来给文字加上 class,但需要该方案能得到广泛支持,否则迁移上是个问题。PHP Markdown Extra 使用的语法是 [link](url){#id .class} ,但在支持上目前也还是个问题,试了几个软件跟在线平台,都还不支持。

那如何在不改变 Markdown 语法,也不影响阅读和迁移的情况下,方便地来自定义样式?

我的想法

我的想法是利用 HTML 的注释 <!--注释--> 来自定义样式,如:

<!--.bg-blue-->

弗朗茨·李斯特(Franz Liszt,1811年10月22日-1886年7月31日),著名匈牙利作曲家、钢琴家、指挥家,是**浪漫主义前期**最杰出的代表人物之一。

有了这行注释,我们就知道要将接下来的一段内容一起解析为如下 HTML :

<div class="bg-blue">
    <p>弗朗茨·李斯特(Franz Liszt,1811年10月22日-1886年7月31日),著名匈牙利作曲家、钢琴家、指挥家,是<strong>浪漫主义前期</strong>最杰出的代表人物之一。</p>
</div>

HTML 注释不会影响 Markdown的内容与阅读,这样的注释表达也挺符合前端的语义。至于解析,实现上问题也不是很大。

更进一步

上面给的例子是针对单一段落,也可以应用到连续的多个段落:

<!--begin.bg-blue-->

弗朗茨·李斯特(Franz Liszt,1811年10月22日-1886年7月31日),著名匈牙利作曲家、钢琴家、指挥家,是**浪漫主义前期**最杰出的代表人物之一。

《钟》(意大利语:La campanella),或译作《康派涅拉》,是弗朗茨·李斯特创作的《帕格尼尼大练习曲》6首中第3首的钢琴独奏曲。

<!--end.bg-blue-->

还可以支持多个自定义样式:

<!--.bg-blue.text-red-->

...

从而在不影响语义和阅读,若不支持也不会影响到解析的情况下,就可以大大增强内容的显示效果了:

<!--.info-->

*弗朗茨·李斯特(Franz Liszt)*: 著名匈牙利作曲家、钢琴家、指挥家,是**浪漫主义前期**最杰出的代表人物之一。

解析为:

<div class="callout callout-info">
    <em class="callout-title">弗朗茨·李斯特(Franz Liszt)</em>
    <p>著名匈牙利作曲家、钢琴家、指挥家,是<strong>浪漫主义前期</strong>最杰出的代表人物之一。</p>
</div>

效果如下(需要自己定义 callout callout-info 的样式):

Markdown 内容增强-自定义样式效果Markdown 内容增强-自定义样式效果

可以看出,灵活借助 HTML 注释,在不影响原有 Markdown 内容的情况下,可以极大丰富内容的样式表现力。

实现 – WordPress 插件

利用正则表达式,可以实现上述讨论的效果。

以 WordPress 为例,WordPress 的文章内容存储在数据库中的字段 post_content 时,只有段落信息,在通过 the_contents() 进行输出时,才会经过 HTML 转换,添加 <p></p> 等标签,因此我们可以在 the_contents() 处理后,再进行一次处理,将 HTML注释 转换成相应的样式。这样子也不会影响原来存储在数据库中的内容。

使用Wordpress提供的 the_content filter即可:

add_filter( 'the_content', 'markdown-extra-customize-attribute' ), 20 );

function markdown-extra-customize-attribute( $content ) {
    $content = preg_replace_callback( '#<p><!--(?=\.)(.*?)--></p>[\s\S]*?<p>(.*?)</p>#', 'para_add_custom_class', $content );
    $content = preg_replace_callback( '#<p><!--begin(.*?)--></p>([\s\S]*?)<p><!--end\1--></p>#', 'multi_para_add_custom_class', $content );

    return $content;
}

function para_add_custom_class( $matches ){
    $class = str_replace('.', ' callout-', $matches[1]);
    if ( strpos($matches[2], '<em>') === 0 ) {
        $matches[2] = preg_replace('#^<em>(.*?)</em>[\:,\.\!:,。! ]*([\w\W]*)#', "<em class=\"callout-title\">$1</em><p>$2</p>", $matches[2]);
    }

    return sprintf('<div class="callout%s">%s</div>', $class, $matches[2]);
}

function multi_para_add_custom_class( $matches ) {
    $class = str_replace('.', ' callout-', $matches[1]);
    if ( strpos($matches[2], '<p><em>') !== false ) {
        $matches[2] = preg_replace('#<p><em>(.*?)</em>[\:,\.\!:,。! ]*#', "<em class=\"callout-title\">$1</em><p>", $matches[2]);
    }

    return sprintf('<div class="callout%s">%s</div>', $class, $matches[2]);
}

Demo

实际效果

你现在看到的就是实际的效果~

GitHub 主页

我把插件放到了 GitHub 上:https://github.com/powerxing/Markdown-Extra-Customize-Attribute

  1. 博主,你的代码高亮部分好棒,请问可以指导一下怎么实现的吗?