Jekyll 基于 Feed 实现静态搜索
折腾 jekyll 有一段时间了,也积累了一点经验,趁还没忘赶紧记录下来,分享给大家。
今天先来说说静态搜索的实现。由于静态博客没有后台,目前站内搜索大多使用 Google 自定义搜索,我之前一篇文章也提到过如何添加,缺点是 Google 在国内访问速度慢且不稳定。最近百度也推出了站内搜索,据说可以搜到没被收录的内容,可是目前还处于内测中,小博客没试用机会。
在网上转了一圈,发现有几篇文章提到先生成一个含有标题、日期、链接等信息的文件 search.xml
,然后用 js 搜索这个文件的方法,但是这种方法功能太过简单。换英文搜了一下,发现有人提出了一个类似的但功能更完善的方法 —— 搜索 RSS Feed 或 Atom Feed。这种方法好处在于不用生成额外的文件,而且可以通过 Feed 中的内容摘要来搜索文章内容。本博客目前使用的就是这种方法,还有人专门建了一个网站为静态博客提供这种服务。
一、定义表单
因为表单不是真的要提交,只需要一个输入框就可以了,还可以加上一个按钮。
<form id="search-form">
<input id="query" type="text" placeholder="正则搜索"></input>
<button type="submit"><i class="icon-search"></i></button>
</form>
二、定义相关div
因为使用 ajax 获取 feed 的 xml 文件,经过处理后将匹配的结果展现出来,所以需要定义两个 div,其中 #loader
显示一个动态图表示正在加载,#main-content
用来显示匹配的结果。
<div id="loader"><img src="{{ ASSET_PATH }}/img/loading.gif" alt="请稍侯"></div>
<div id="main-content"></div>
二、编写js代码
代码不长,应该很容易看懂。大致思路是响应表单的提交事件,发出 ajax 请求,调用 findEntries
进行匹配。几个注意事项:
- 由于使用了 jQuery,需要链接 jQuery 库文件。
- 此处使用的是 Atom Feed,如果你用的是 RSS Feed,
findEntries
需要做一些修改,因为两者格式不一样。 - 此处使用正则表达式进行搜索,想搜索
c++
,应该输入c\+\+
。如果不想使用正则表达式,请自行修改。
$(document).ready(function() {
var entries = null;
function formatDate(date) {
var monthNames = [ "January", "February", "March", "April", "May", "June",
"July", "August", "September", "October", "November", "December" ];
return date.getDate() + ' ' + monthNames[date.getMonth()] + ' ' + date.getFullYear();
}
function findEntries(q) {
var matches = [];
var rq = new RegExp(q, 'im');
var rl = /^http:\/\/liberize\.me\/post\/(.+)\.html$/;
for (var i = 0; i < entries.length; i++) {
var entry = entries[i];
var title = $(entry.getElementsByTagName('title')[0]).text();
var link = $(entry.getElementsByTagName('link')[0]).attr('href');
var title_en = rl.exec(link)[1].replace(/-/g, ' ');
var content = $(entry.getElementsByTagName('content')[0]).text();
if (rq.test(title) || rq.test(title_en) || rq.test(content)) {
var updated = formatDate(xmlDateToJavascriptDate($(entry.getElementsByTagName('updated')[0]).text()));
matches.push({'title': title, 'link': link, 'date': updated, 'content': content});
}
}
var html = '';
for (var i = 0; i < matches.length; i++) {
var match = matches[i];
html += '<article class="nested">';
html += '<header><h2><a href="' + match.link + '">' + htmlEscape(match.title) + '</a></h2></header>';
html += '<section><p>' + htmlEscape(match.content) + '</p></section>';
html += '<footer><p>更新日期:' + match.date + '</p></footer>';
html += '</article>';
}
$('#main-content').html(html);
$('#loader').hide();
$('#main-content').show();
}
$('#search-form').submit(function() {
var query = $('#query').val();
$('#query').blur().attr('disabled', true);
$('#main-content').hide();
$('#loader').show();
if (entries == null) {
$.ajax({url: '/atom.xml?r=' + (Math.random() * 99999999999), dataType: 'xml', success: function(data) {
entries = data.getElementsByTagName('entry');
findEntries(query);
}});
} else {
findEntries(query);
}
$('#query').blur().attr('disabled', false);
return false;
});
});
其中,htmlEscape 函数用来对内容做 html 转义,xmlDateToJavascriptDate 函数用来将 feed 中的 xml 格式的日期转为 js 中的 Date 对象。
完整代码见 Gist:https://gist.github.com/liberize/7575100。
第一次搜索因为需要取回 xml 会比较慢,后面每次搜索都非常快。另外,使用这种方法不建议将 feed 设为全文输出,因为这会导致取回和搜索都比较慢,尤其是在文章很多的情况下。
为简单起见,没有加入返回搜索前页面的功能,可以使用 history.js
来实现。