手记7:从单人博客到多人博客全程记录

上一篇手记是讲“改造Gitment”的(在这里)。其实这个“改造”花了不少功夫,以至于后来还出了一个给hexo-theme-next的版本。

这是推迟了“做多人博客”的一个原因,另一个原因则是我以前的博文需要更新的太多,花了很多时间。当然,那个叫Monster的项目(在这里)也是花销时间的大户。

所以,结果就是:这篇“手记7”被推迟了很久...很久...

多人博客的功能其实我一早就做出来了——至少,大概的框架是完成了的。但是我一直没有发布出来,“麦秸的垛”也就一直没上线。

下面讲讲这整个的过程。

导入“第二个人”的博客文章

在Ghost 0.9x中其实有一个Bug,就是你总是只能将博客导入到'id=1'的帐户(或以该帐户身份导入)。我说不清这个Bug是什么时候存在,又或者(可能)什么时候会修复/已修复。我当时找到的解决方法,就是直接改sqlite数据库。

不过自从我开始启用Ghost 1.x之后,这个担忧就没有了。在多人博客这个问题上,Ghost 1.x做得其实更好,所以我后面也是在用这个版本,关于0.9x的更多细节就不谈了。

假如你已经有了一个基于Ghost的本地博客,已经有一个帐户了,那么当你需要把它变成“多人博客”时,你自然需要再创建一个帐户——这个不用多说了。一旦你搞定了,那么你可以用monster来查询到多个用户的信息:

> monster list user
id                        name        slug        email         
------------------------  ----------  ----------  --------------
1                         aimingoo    aimingoo    aiming@gmail.com 
5951f5fca366002ebd5dbef7  second      new-user    ...
...

记下新用记的id值(5951f5fca366002ebd5dbef7),后面我们会用到。

注意:

在Monster v1.0.7以下的版本中list user只会显示被截断的user id。因此要正确地查询这个id值,请将你的Monster升级到v1.0.7或更高版本——或者你也可以自己打开sqlite数据库查看。

接下来,假设你是用BlogToWordpress来迁移的博客,那么你会有一个wordpress格式的.xml文件,存放用户second的全部博客——如果你是用别的来导出,那么请转换到wordpress格式先。参考:

我们将这个.xml用工具wp2ghost转换到ghost-tmp.json,然后再使用putrefy.js来处理一次,得到最终的ghost.json就可以了。这个过程参考:

但还是要注意以下几点:

使用修改版的wp2ghost

你可以考虑使用我的修改版wp2ghost。主要有三处变化:

  • tag忽略大小写
  • 忽略掉总是存在的DefaultCategory这个tag
  • 兼容用wp:wp_authorwp:author来识别作者名

下载在这里:

配置putrefy.js

如果你只是简单导入,那么你可以直接使用putrefy.js命令行传入Ghost用户的id值(上面找到的那个)。例如:

# 获取 putrefy.js
> curl -L https://github.com/aimingoo/ghost-utils/raw/master/putrefy.js -o putrefy.js

# 处理ghost-tmp.json
> node putrefy.js ghost-tmp.json '5951f5fca366002ebd5dbef7' > ghost.json

不过注意,很多情况下putrefy.js不是直接使用的,它几乎总是需要修改配置才能使用(除了上面的这个new_author_id参数外,就只能直接修改源代码了)。

最后,参考上面的博客说明,你应该可以顺利地用putrefy.js了。

多博客的一点说明

在使用putrefy.js时,它缺省为你的post生成的slug将是authorId-postId格式的,例如上面的5951f5fca366002ebd5dbef7,就会生成5951f5fca366002ebd5dbef7-1.....-n这样的格式。

你可以改这个缺省值改成特定风格。例如2-,或者myblog:这样的前缀。这需要直接修改putrefy.js源代码中的SLUG_FROMID变量值。如下:

## 参数值:
##   - "字符串": 以postId作为slug,并将该字符串值直接作为前缀
##   - true: 以postId作为slug,并用当前的"authorId-"作为前缀,authorId值缺省为1
##   - false: 不修改slug

SLUG_FROMID="2-"

另外,某些情况下,从wordpress格式的.xml文件中导出的authorId值不总是缺省值1(或者可能因为意外的缘故就没有authorId值),这种情况下,你需要修改putrefy.js代码中的

var author_map = {
  "1": ...
}

它可以处理多个authorId,或强制指定转换表。

在你的Ghost模板中识别多个作者

有两种方法来识别多个作者。

第一种是直接在模板中使用{{author}},这个标签对于author、page和post等页都是有效的,因此可以方便地使用这个标志来为不同作者切换内容:

<!-- 可以直接用`{{#has ...}`来识别 -->
{{#has author="aimingoo, qomo"}}
 ....
{{/has}}

但是这不能作用于{{> ..}}标签来装载的子模板——你当然可以写多个has来识别,但这并不方便。因此,我在博客中选择了第二种方案:通过加载<script>标签来处理。

这个名为author-switch.js的脚本在这里:

可以通过如下的方式来加载:

<script type="text/javascript" author="{{author.slug}}" src="/assets/js/author-switcher.js"></script>

<script type="text/javascript" author="default" src="/assets/js/author-switcher.js"></script>

author-switcher.js中会读取所有scripts并查找到这个标签,然后从标签中取出author属性,并针对不同的author来处理当前页面的显示——基本上就是调整导航栏、标题页等等。

author-switcher.js中有部分代码显得比较复杂,主要是根据deviceWidth来处理导航栏显示的(用于适应手机等窄屏设备)。

你不能直接使用author-switcher.js,它需要根据你的网站定制

静态页的处理

一些静态页是多个作者之间不同的,例如A作者可以显示某些标签,而B作者就不需要(你想想如果在麦秸的垛的文章里显示出JavaScript这样的标签会是什么效果)。因此我除了将tag-cloud等页面静态化(处理方法在这里)之外,也在其中加入了相应的代码来处理这种静态页内的信息。例如:

<!-- in page-tag-cloud.hbs -->

var enabledCloudTags = {
    'default': 'all',
    'aimingoo': 'all',
    'more users': [ "some tags for other', ...]
}

function filterByAuthor(context, enabled) {
    if (enabled == 'all') return context;
    ...
}

相应的代码在这里:

注意其中的cloudTagHtmlContext()内的代码是由标签{{> "tag_cloud"}}自动生成的,参考:'7.2.1 为标签云添加一个静态页' @这里

多作者多线索(Thread)

Ghost缺省的多作者模式是“多作者单线索”的,也就是所有作者的prev/next是在同一个list中,这样A作者的上/下一篇日志,就可能是B作者的。对于同一主题的博客(例如相同兴趣的网站)来说这没问题,但是我跟Joy的博客风格差异很大,你想想在一篇讨论户外驴友美食或化妆的文章中出现“上一篇”是某个开源程序项目的……效果,那真的是……

好吧。所以我需要跟Joy的博客使用“多作者多线索”的风格。这个是早先的Ghost版本中是不支持的,因为它默认的prev/next helper是只支持单线索的。这个问题已经被提了两年了……没解决过。

很幸运的事情是,正好在“麦秸的垛”上线前一两天,Ghost v1.14.0发布了。这个版本中带了一个primary tag的特性,它的实现方案令人眼前一亮啊(呵呵)。“多作者多线索”的实现立即变得简单了,于是我加了几行代码,现在你可以在post.hbs里这样写:

{{! -- MUST include "author" field -- }}
{{#get "posts" filter="id:{{id}}" include="author" as |current|}}
{{#current}}

  {{#prev_post in="author"}}  ...
  ...

  {{#next_post in="author"}} ...
  ...

{{/current}}
{{/get}}

就可以了。——注意其中的include="author"in="author"

这个简单的feature已经提交到Ghost team了(在这里),如果你等不及的话也可以直接用我fork的版本:https://github.com/aimingoo/Ghost/tree/prev-next-author