介绍我用纯JS实现的一个静态站点评论系统,以及实现过程中的心得体会。
我的博客最早是使用 Disqus 来实现评论功能的。Disqus 被墙了之后,改成了多说。今年年初,多说也正式关闭了,于是我被逼着又开始寻找其他的替代评论系统。
我先是试用了网易云跟贴、畅言等几种类似的社会化评论系统。畅言要求站点必须备案,而我实在没有为了评论去申请备案的动力。网易云跟贴的管理后台上有很多不明觉厉的功能,但好像都没多大用处。最致命的问题是我不小心把我的站点绑定到了另一个网易账户,而不是我常用的微博账户。这样的话,我每次回贴就得退登到微博账户,要管理贴子的时候又得切回管理员账户,非常不方便。然而网易云跟贴并没有提供解绑的功能。于是我给他们提了需求,然而一直到现在都没有回复。再加上有了多说作为前车之鉴,我对国内的免费评论服务已经失去了信心。今天把A换成B,难以保证日后B也关闭了,被逼着又换到C,实在是懒得折腾下去啊。于是,我放弃了换用类似的评论系统的念头。
之后我找到了 isso 项目,它是一个 Python 实现的开源评论服务。这个服务需要搭建在自己的服务器上。官方的简介简明扼要:“a Disqus alternative”。出于对 Python 的好感,我把站点的评论功能迁移到了 isso 。然而,我对 isso 也并不是很满意。首先它的功能其实也非常弱,不支持 Markdown 语法,不支持 Gravatar 头像,也没有一个像样的管理后台,搭建和配置的过程也比较费时,远达不到开箱即用的程度。再加上 isso 需要服务器运营,为了一个评论系统而去购买服务器确实太奢侈了。用了几个月后,我又萌生了换掉它的念头。
我的想法来源于一些基于 Github issue 的博客。其实 Github 的 issue 本身就是一个非常完善的评论系统,有完善的管理后台,灵活的通知设置,而且 Github 是开放 API 的。只要我能把 Github 的 issue 与博客的页面打通,把 issue 上的内容显示在我的博客上,然后在需要评论的时候点击跳转到 Github 的 issue 页,就实现了一个基本可用的评论系统了。
comment.js 就是基于这个想法实现的一个评论系统,它的核心代码只有 400 行左右,却能够用来实现评论会话和最新评论列表的两个功能。比起已有的社会化评论系统,它有如下几个优点:
comment.js 依赖几个 JS 前端库:
在页面中添加这些资源:
1 | <!-- stylesheet --> |
为了避免 API 被恶意滥用,Github API (以及 OSChina API)设定了一个API调用频率限制。为了提高频率限额,建议 [注册一个 Oauth App](Register a OAuth application](https://github.com/settings/applications/new)。
完成注册后,你将得到一个 client id
以及一个 client_secret
,先将这两个值记下来,后面我们会用到。
(提示:注册 App 的时候你可能会对 Authorization callback URL
这一项目感到困惑,一般填写你的站点地址即可。例如 http://hahack.com )
第一步,在页面中添加一个 DIV ,用于展示评论会话内容。
1 | <div id="comment-thread"></div> |
第二步(可选),如果希望在加载完数据前先展示一个loading动画,还可以添加一个用于动画的 DIV :
1 | <div id="loading-spin"></div> |
最后,调用 getComments()
方法,获取该页面对应的 issue 包含的所有评论,然后展示到我们指定的 DIV 中:
1 | <script type="text/javascript"> |
参数说明:
type
: 要作为后端的站点。目前支持 Github
和 OSChina
。user
: 您的 Github 用户名。repo
: 您用作评论后端的仓库名。no_comment
: 当没有评论时,展示的提示消息。go_to_comment
: “去留言” 按钮的按钮文本。issue_title
: 您当前页面对应的 issue 标题。也可以使用 issue_id
,二者只选其一。issue_id
: 您当前页面对应的 issue id。也可以使用 issue_title
,二者只选其一。btn_class
: “去留言”按钮的 CSS 样式名。comments_target
: 用于展示评论内容的容器。例如我们上面所写的 comment-thread
DIV 。loading_target
(可选):用于展示 loading 动画的容器。例如我们上面所写的 loading-spin
DIV 。client_id
(可选但建议):您注册的 OAuth App 的 client id。client_secret
(可选但建议):您注册的 OAuth App 的 client secret。效果参见本页面下方的留言区。
评论列表用于获取你最近的若干条评论,效果可以参见 站点首页 右侧的最新留言区。
要获取最新评论列表的方法也大同小异。首先写一个 DIV 用于加载获取得到的评论列表数据:
1 | <div id="recent-comments"></div> |
之后可以调用 getRecentCommentsList()
方法,获取最近评论列表并展示到指定的 DIV 中。
1 | <script type="text/javascript"> |
参数说明:
type
: 要作为后端的站点。目前支持 Github
和 OSChina
。user
: 您的 Github 用户名。repo
: 您用作评论后端的仓库名。recent_comments_target
: 用于展示最新评论列表的容器。例如我们上面所写的 recent-comments
DIV 。count
: 列表的最大长度。client_id
(可选但建议):您注册的 OAuth App 的 client id。client_secret
(可选但建议):您注册的 OAuth App 的 client secret。下面照例总结下项目的开发心得。虽然整个项目只有几百行的代码,但这个过程中还是不可避免的遇到一些困难。
一开始的想法只是给 Hexo 写一个插件,让其能够实现评论功能。最理想的情况是类似 hexo-generator-search 那样,npm install 一下,然后 _config.yml 里添加下配置就完事。通过阅读 Hexo 的文档后我发现 helper 似乎比较适合用作这个目的:把核心功能写成一个 helper ,然后在模板文件里直接执行这个 helper ,得到的数据还能进一步再模板中调诸如 markdown 等其他现成的 helper, 这样还能实现 Markdown 支持。于是我最初的项目仓库名叫做 hexo-helper-github-comment 。
等我实现了 getComments()
方法后,我发现我的想法是错误的:helper 只适用于同步执行的操作,不适合网络请求这种异步操作。这带来的问题就是模板文件里已经成功执行了 helper 了,也返回了数据,但此时 renderer 早已经完成了模板的渲染了,而异步返回的评论数据却不再能够被渲染。
之后我想在 NodeJS 中加入 jQuery,用 jQuery 来操纵 DOM ,而不再依赖 renderer 。但这个方案似乎也不可行。因为在模板文件中,DOM 还没有创建,jQuery 拿不到实际的 DOM 。
所以最终我改成了纯 JS 的方案,把请求的方式也从 request-promise 改成了 AJAX ,然后在模板文件中直接跑 JS ,让 JS 完成请求,此时的 DOM 是已创建的,可以使用 jQuery 来操纵页面。虽然这样做就不能直接用 Hexo 现成的 markdown helper 了,但由于是纯 JS 实现,这个库也就可以在任何静态站点中使用,变得更加通用了。于是我把仓库名改成了 github-comment 。
又后来,我准备开源的前一天,在微博上先公开了关于这个项目的信息。有些人也表示了 Github 将来也可能被墙的质疑。于是我花了几分钟时间,也加入了对 OSChina 的支持。这个仓库名似乎也不只是基于 Github 了,于是我又把仓库名改成了 comment.js 。
我最纠结的部分,在于要不要把评论框也写进来。
直接在页面中写评论,减少了页面的跳数,当然是一大收益。但这样做也有几个问题:
有意思的是,当我刚发布 comment.js 的时候,我才发现几个月前已经有人做了一个类似的项目:gitment,真是心有灵犀啊。这个项目与我的项目的最大区别就在于它实现了内置的编辑框,并且目前只支持 Github 。如果你认为评论框必不可少,那么建议使用 gitment;反之如果你觉得点击按钮跳到 Github 页面似乎也还能接受,担心 Github 单点问题,而且觉得保证代码的简单和通用性更重要的话,那么不妨使用 comment.js 。