基于jQuery的方案,为Ruhoh生成目录。

最近扔了不少笔记在 wiki 上。笔记一多就出现了查找定位的问题。比如,当我学了 awk 后想回过头来再看一下 mysql ,我可能就已经忘记了 mysql 的笔记的结构,于是为了定位到某个关键词,我可能得花半天时间在查找上。我想这种情况会随着我日后笔记的增多而越发严峻,所以最好就现在就动手解决它。

最简单有效的办法是实现站内搜索功能。ruhoh这类静态博客的一个缺点是没有自带搜索的能力。一个解决方案是动用 Google 来生成站内搜索。实际上 Google 的自定义搜索还可以进行一些定制,不过最简单的样式已经满足我的需求,我把它加到了 wiki 首页的底部。

但站内搜索并不完美。首先这个搜索依赖于 Google 的抓取,所以一些刚写不久的笔记如果还没被 Google 抓取到的话是搜不到的。另外一个问题是这本质上并不能避免我忘记笔记结构的问题,反而是更容易恶化这个情况:有了搜索引擎的支持,我可能就会在笔记内容的组织上更加随意。因为不管我怎么写,Google 都能帮我找出来。这好比一个仓库:原先我自己亲手打理,每样物品的摆放我都要求井井有条。后来我请了个下手帮我去仓库找东西,这个下手也很任劳任怨,每次我需要某个物品的时候他都可以找出来。于是,我放物品的时候也开始变得随意起来了。反正不是我来找,管他呢?时间久了,我肯定会忘记仓库里的一切。当有一天下手不干活了 11这种情况还经常发生呀!,或者我决定要考验下自己尚能饭否,非得要亲自要到仓库里取一样东西,我很可能会在仓库里迷路。

于是,最好的做法是再雇一个人,在我每次放东西的时候,他负责记录我把东西放在哪个区域。这样即使我亲自去找某个物品,根据他给的记录,我也可以大致判断该走哪个方向。这就是目录(Table of Contents, ToC)的作用:通过为我的每篇笔记生成一个目录,我可以对每篇笔记的结构和脉络有个更加清晰的印象,这有助于对知识结构的整理和回顾。同时,我也会更加认真地组织一下内容,以期让目录也尽可能的保持清晰。 < 要生成目录,目前大致有三种方案:

  1. 直接利用Converter生成ToC。例如我使用的是Pandoc,在render的时候可以加上 --toc 选项,就可以在生成的页面开头带上目录。但这种方式最不灵活,它生成的位置只能在文章顶部,这对于我想把ToC生成在页面左侧的栅格内是不可行的。
  2. 利用 Converter + 模板语言生成 ToC。这种方案实质上还是基于第一种方案,例如这篇文章《Ruhoh 目录生成随想》的方法。通过这么定制,ToC的位置就变得容易调整了。但这个方案的缺点是会影响页面的编译效率,需要先用 Converter 生成 ToC,然后才能交给 Mustache 来将生成的 ToC 作为 partial 插入到文中的指定位置。
  3. 使用jQuery的ToC插件来生成ToC。这个方案的方式比较特别:ToC并不是直接生成到页面里的,而是在页面加载完后才交由jQuery渲染出来的。

经过仔细对比,我选择了第三个方案。一个最重要的原因就是它可以完整保留原来的页面结构,并不会在页面源码中插入目录。如果我以后想从HTML页面转换回Markdown或者其他格式的话会方便一些。

使用方法

安装配置 jQuery

下载 jQuery,把下载下来的 jquery.1.x.min.js22由于 jQuery 2.x 不支持 ie 6、7或8,因此建议下载 1.x。 放到主题目录下的 javascript 目录下。然后在 default.html 中添加引用。

安装配置 ToC 插件

下载 Table of Contents jQuery Plugin,同样放置在 javascript 目录下。

当然直接参照该插件的示例代码就可以生成 ToC 了,但为了更加灵活,可以为文档提供一个 toc 选项,如果为 true 就生成目录,否则就不生成目录;另外还可以添加一个 startLv 选项,用以指定从第几级标题开始生成目录。因此可以写一个 js 脚本 33代码参考了这篇文章,保存为 javascript/tocgenerator.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function generateTOC(insertBefore, heading, startLv) {
var container = jQuery("<div id='tocBlock'></div>");
var div = jQuery("<ul id='toc'></ul>");
var content = jQuery(insertBefore).first();

if (heading !== undefined && heading !== null) {
container.append('<span class="tocHeading">' + heading + '</span>');
}

if (startLv === undefined) { startLv = 1; }

div.tableOfContents(content, { startLevel: startLv });
container.append(div);
container.insertBefore(insertBefore);
}

默认会从第一级标题开始一直生成到到第六级。如果只想解析第二级标题和第三级标题,只需要指定深度为2。可以把上面的代码改为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function generateTOC(insertBefore, heading, startLv) {
var container = jQuery("<div id='tocBlock'></div>");
var div = jQuery("<ul id='toc'></ul>");
var content = jQuery(insertBefore).first();

if (heading !== undefined && heading !== null) {
container.append('<span class="tocHeading">' + heading + '</span>');
}

if (startLv === undefined) { startLv = 2; }

div.tableOfContents(content, { startLevel: startLv , depth: 2 });
container.append(div);
container.insertBefore(insertBefore);
}

当然再给文章加一个 depth 选项也是可行的,但是 Mustache 这货的逻辑处理能力巨弱,连逻辑与或非操作也没有44难怪叫做 Logic-Less Template --bb 。因此,多加一个可选的选项就会在下面要介绍的 Mustache 模板中多加一层嵌套,代码就会写的很冗长。是否扩展它由读者自行斟酌。

之后需要在 default.html 中引用 ToC 插件。跟 Octopress 的做法不同,实现这个需要用到 Mustache 模板语言。在 footer 处加入:

这样,对于需要生成目录的文章,只需要在开头部分添加 toc: true ,以及设置一下生成目录的初始标题级别 tocstartlv(可选),就可以在页面顶端生成目录了。对上面的 tocgenerator.js 稍加修改可以让其放在文档中的任意位置。我的笔记的目录都统一放在了左侧的栅格中,例如这篇笔记

最后还可以给草稿添加scaffold,在posts目录(或者你自己定义的collection)新建或修改 _scaffold ,内容如下:

1
2
3
4
5
6
7
---
title:
categories:
data: '{{DATE}}'
toc: true
tocstartlv: 2
---

这样每次当你调用 draft 命令时,ruhoh就会按照上面的设置新建一篇草稿。记得把上面的 tocstartlv 设为你自己需要的初始标题级数。

Comments