花了相当相当多时间重新建设自己的内容管理系统,总算大致完成(https://github.com/huangjunwen/mdtool),记录一下
需求
大致如下
- 内容应该可以很容易跨平台发布,例如除了本身作为博客的网站外,也能发布到微信公众号或其他内容平台, 甚至可以单独输出成文档
- 写作时要有实时反馈
- 内容使用的格式不能过于依赖专有或特定软件
- 仅使用开源工具
格式
Markdown
熟悉,简单,广泛应用
Tex
功能强大,表达丰富,不过需要些时间学习;另外 markdown 中也可以嵌入 math mode 的公式,这已经解决很大一部分问题了
Svg
缩放无损,文本可编辑,众多编辑器可用1,可 apply css
PNG
普遍支持
工具
Hugo
速度快,编辑即可看到效果
Pandoc
pandoc 也是可以作为 hugo 的 markdown 渲染引擎的 2,用它有多个好处
- 能识别 latex 式的 math mode(
$...$
/$$...$$
) - 可以写 filter!用 外部程序 或者 lua 脚本 直接修改 pandoc 的 ast
- pandoc 本身就是用来转换格式的(e.g. markdown -> pdf)
- 更多扩展
整合
整合起来就是:内容为 markdown 格式,尽量少使用专有的扩展,pandoc 负责将其转换格式,其中最主要的 html 格式则交由 mdtool(hugo + js + css)进一步处理及呈现
一些实现细节:
- hugo 调起 pandoc 的命令行很简单:
pandoc --mathjax
,因此需要自己写一个也叫做pandoc
的 shell 脚本并放置到$PATH
更优先的地方,在其中可添加更多的参数(e.g. 添加 filter) - 一开始是在 pandoc filter 里用 prismjs/mathjax 对语法高亮/数学公式进行服务端渲染,完成后发现太慢了,后来还是改回客户端渲染
- 微信公众号对 css 有巨多限制,给调试样式添加了很多麻烦
assets
有时图片或其他 asset 需要由别的文件转换生成(e.g. dot 转 svg,svg 转
png 等),不想每次都手动转换的话,
可以简单写个脚本监听文件变化然后进行处理;例如下面是一个将 ipe
格式的文件转换为 svg/png 的脚本, inotifywait
监听特定文件名模式(*.svg.ipe
表示想转为 svg,*.png.ipe
表示想转换为
png),匹配到更新时, 就用 iperender
和 inkscape
对图片进行处理转换
注:这里用 inkscape 主要是用到它 clone-unlink-recursively 功能, svg 里为了节省空间常常会定义 defs 然后别的多个地方 use 引用(clone)它, 自然地 use 的地方需要指定 defs 的 id,但恶心的是微信公众号环境下是不允许有 id 的, 故不能用 use 而只能每一个出现的地方都复制一遍
#!/bin/bash
# gen-files.sh
ipe2svg () {
# *.svg.ipe -> *.svg
local src=$1
local target=${src%.ipe}
if [ $src -nt $target ]; then
iperender -svg $src /dev/stdout | \
inkscape -lp --export-filename=- --actions="select-all:all;clone-unlink-recursively" --vacuum-defs | \
sed 's/rgb(0\%,0\%,0\%)/currentColor/g' > $target
echo "$target is regenerated"
else
echo "$target is already up-to-date"
fi
}
ipe2png () {
# *.png.ipe -> *.png
local src=$1
local target=${src%.ipe}
if [ $src -nt $target ]; then
iperender -png -resolution 256 $src $target
echo "$target is regenerated"
else
echo "$target is already up-to-date"
fi
}
find . -name '*.svg.ipe' -print | while read file; do \
ipe2svg $file; \
done
find . -name '*.png.ipe' -print | while read file; do \
ipe2png $file; \
done
inotifywait -rmq -e close_write,moved_to . | while read dir action file; do \
case $file in
*.svg.ipe)
ipe2svg $dir/$file
;;
*.png.ipe)
ipe2png $dir/$file
esac
done