使用Tapir实现jekyll的站内搜索功能

要在jekyll搭建的静态网站中实现站内搜索, 网上给出的解决方案基本都是google custom search, 但是这个方案不但有广告, 想修改css的时候又非常的复杂, 难以下手. 下面提供另一种非常简便的站内搜索解决方案–Tapir.

先简单介绍一下Tapir, Tapir是通过你的网站的RSS feed来创建索引, 并且使用Tire (Powered by Elasticsearch, 而底层的实现, 就是大名鼎鼎的Lucene)进行索引并实现搜索的简洁的应用. 并通过JSON API返回搜索的结果.

通过在Tapir官方网站注册RSS feed后获取的token, 进行搜索, 返回的json格式如下:

[
  {
    "title":"Capybara ate Swinger",
    "published_on":"2011-02-07T05:00:00Z",
    "content": [the full article content],
    "link":"http://jeffkreeftmeijer.com/2011/capybara-ate-swinger",
    "summary":"Remember Swinger, the Capybara RSpec driver swapper? Capybara can now swap drivers out of the box.",
    "_score":61.15513
  }
]

你可以现在本站测试一下站内搜索, 中文支持的也非常出色. 下面说一下完整的实现方案:

1. 修改RSS feed格式

编辑你的网站文件夹下的atom.xml(或者别的什么名字的文件), 最好在格式entry里加上summary, 也就是每篇blog的摘要部分, 在搜索结果中展现blog的摘要要比给出全文美观的多.

 <entry>
   <title>{ { post.title } }</title>
   <link href="{ { site.production_url } }{ { post.url } }"/>
   <updated>{ { post.date | date_to_xmlschema } }</updated>
   <id>{ { site.production_url } }{ { post.id } }</id>
   <content type="html">{ { post.content | xml_escape } }</content>
   <summary type="html">{ { post.description | xml_escape } }</summary>
 </entry>
注意: 上面双大括号之间不应有空格, 我是为了现在显示不会被转意才这么写.

2. 登录Tapir官方网站注册你的rss feed, 会返回给你一个token, 记下它.

3. 在模板中给你的网页上下个search框吧, 我用的是jekyllbootstrap框架, 加入这个功能非常容易:

<form class="navbar-search pull-right" action="search.html">
  <input type="text" class="search-query" placeholder="Search">
</form>

由于搜索用到了jquery, 需要加入jquery的js和一个处理tapir搜索过程的js(这个功能比较简单, 当然你也可以自己去实现或修改)

<script src="/assets/themes/dan/js/jquery.min.js"></script>
<script src="/assets/themes/dan/js/jquery-tapir.min.js"></script>
(function($) {
    var el;
    var settings = {};
    var methods = {
        init: function(options) {
            el = this;
            settings = {
                token: false,
                query_param: 'query'
            };
            if(options) {
                $.extend(settings, options);
            }
            if(!settings.token || settings.query_param == '') {
                return this;
            }
            $.getJSON('http://tapirgo.com/api/1/search.json?token=' + settings.token 
                        + '&query=' + paramValue(settings.query_param) + '&callback=?',
                    function(data) {
                if(settings['complete']) {
                    settings.complete()
                }
                $.each(data, function(key, val) {
                    el.append('<div class="result"><h3><a href="' + val.link + '">' 
                        + val.title + '</a></h3><p>' + val.summary + '</p></div>');
                });
            });
            return this;
        }
    };

    function paramValue(query_param) {
        var results = new RegExp('[\\?&]' + query_param 
                + '=([^&#]*)').exec(window.location.href);
        return results ? results[1] : false;
    }
    $.fn.tapir = function(method) {
        if(methods[method]) {
            return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
        } else if(typeof method === 'object' || !method) {
            return methods.init.apply(this, arguments);
        } else {
            $.error('Method ' + method + ' does not exist on jQuery.tapir');
        }
    };
})(jQuery);

Download jquery-tapir.min.js.zip

由于search.html是在加载时直接执行搜索, 所以这两个js文件必须在header中加载.

4. 上面一部form的action为search.html, 下面我们就创建这个页面

---
layout: page
title: Pages 
header: Pages
---

<h2>Search Results</h2>
  <div id="search_results"></div>
<script>
  $('#search_results').tapir({'token': '50a61c823f61b0346e0003a4'});
</script>
token填入前面注册返回的token值.

完成了以上几个步骤, 你的站内搜索已经搭建成功了, 相比google custom search来说, 最大的长处在于, 你可以自由的修改search box和result的样式, 而不用受到google的各种限制.

在vim做粘贴操作时禁用自动缩进和智能缩进

vim的自动缩进和智能缩进功能给在vim下做开发的程序员提供了非常大的便利, 但在做代码粘贴的时候, 如果被粘贴的文字中带有tab, 整个粘贴后的格式会乱掉.

看到一些论坛, 很多vimer也碰到同样的问题, 给出的答案是禁用自动缩和智能缩进功能: .vimrc添加如下配置

set noai
set nosi

但是这样设置之后依然没有解决问题. 继续查资料, 发现还有另外一个设置:

:set paste 

开打paste模式以后, 粘贴问题缩进问题立刻就解决了, 查看帮助, 会发现paste模式做了如下操作:

When the 'paste' option is switched on (also when it was already on):
        - mapping in Insert mode and Command-line mode is disabled
        - abbreviations are disabled
        - 'textwidth' is set to 0
        - 'wrapmargin' is set to 0
        - 'autoindent' is reset
        - 'smartindent' is reset
        - 'softtabstop' is set to 0
        - 'revins' is reset
        - 'ruler' is reset
        - 'showmatch' is reset
        - 'formatoptions' is used like it is empty
These options keep their value, but their effect is disabled:
        - 'lisp'
        - 'indentexpr'
        - 'cindent'

设置了这么多选项, 怪不得只关闭ai和si没有用.

但是这样还是比较麻烦, 难道每次粘贴前都要先执行set paste, 粘贴完成后在执行set nopaste么? 当然不行, 下面要绑定快捷键, .vimrc中添加如下配置:

map <F10> :set paste<CR>
map <F11> :set nopaste<CR> 

这样, 每次粘贴前, 先按F10进入paste模式, 粘贴后再按F11退出粘贴模式, 但是这样又占用了两个快捷键, 也不是很方便. 其实, paste有一个切换paste开关的选项, 这就是pastetoggle. 通过它可以绑定快捷键来激活/取消paste模式. 比如:

set pastetoggle=<F11>

这样减少了一个快捷键的占用, 使用起来也更方便一些.

Sublime Text的Package Control插件配置proxy

Sublime Text中的default的package control中对proxy似乎并没有生效, 始终无法浏览插件列表, 查了不少资料, 应该是对https的代理设置上有点问题. 下面说一下解决方案.

尝试修改package control中的repositoryhttps改为http:

From:

"repository_channels": [
"https://sublime.wbond.net/repositories.json"
]

To:

"repository_channels": [
"http://sublime.wbond.net/repositories.json"
]

File: Package Control.py

From:

url = download['url']

To:

url = download['url'].replace('https','http')

最后, 在配置package control的http_proxy应该就没有问题了

解决Jboss报出 URL pattern /Coordinator is already registered 错误

部分web项目在jboss7.0.x工作正常, 但是迁移到7.1的时候报出URL pattern /Coordinator is already registered, 报错信息如下:

10:22:06,097 ERROR [org.jboss.msc.service.fail] (MSC service thread 1-3) MSC00001: Failed to start service jboss.deployment.unit."snaLight.war".PARSE: org.jboss.msc.service.StartException in service jboss.deployment.unit."snaLight.war".PARSE: Failed to process phase PARSE of deployment "snaLight.war"
          at org.jboss.as.server.deployment.DeploymentUnitPhaseService.start(DeploymentUnitPhaseService.java:119) [jboss-as-server-7.1.0.Final.jar:7.1.0.Final]
          at org.jboss.msc.service.ServiceControllerImpl$StartTask.startService(ServiceControllerImpl.java:1811) [jboss-msc-1.0.2.GA.jar:1.0.2.GA]
          at org.jboss.msc.service.ServiceControllerImpl$StartTask.run(ServiceControllerImpl.java:1746) [jboss-msc-1.0.2.GA.jar:1.0.2.GA]
          at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886) [rt.jar:1.6.0_27]
          at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908) [rt.jar:1.6.0_27]
          at java.lang.Thread.run(Thread.java:662) [rt.jar:1.6.0_27]
Caused by: java.lang.IllegalArgumentException: JBAS015533: Web Service endpoint com.sun.xml.ws.tx.webservice.member.coord.ActivationRequesterPortTypeImpl with URL pattern /Coordinator is already registered. Web service endpoint com.sun.xml.ws.tx.webservice.member.coord.RegistrationRequesterPortTypeImpl is requesting the same URL pattern.
          at org.jboss.as.webservices.metadata.model.AbstractDeployment.addEndpoint(AbstractDeployment.java:60)
          at org.jboss.as.webservices.metadata.model.JAXWSDeployment.addEndpoint(JAXWSDeployment.java:27)
          at org.jboss.as.webservices.deployers.WSIntegrationProcessorJAXWS_POJO.processAnnotation(WSIntegrationProcessorJAXWS_POJO.java:94)
          at org.jboss.as.webservices.deployers.AbstractIntegrationProcessorJAXWS.deploy(AbstractIntegrationProcessorJAXWS.java:87)
          at org.jboss.as.server.deployment.DeploymentUnitPhaseService.start(DeploymentUnitPhaseService.java:113) [jboss-as-server-7.1.0.Final.jar:7.1.0.Final]
          ... 5 more
 
 
10:22:06,112 INFO  [org.jboss.as.server] (DeploymentScanner-threads - 1) JBAS015870: Deploy of deployment "snaLight.war" was rolled back with failure message {"JBAS014671: Failed services" => {"jboss.deployment.unit.\"snaLight.war\".PARSE" => "org.jboss.msc.service.StartException in service jboss.deployment.unit.\"snaLight.war\".PARSE: Failed to process phase PARSE of deployment \"snaLight.war\""}}
10:22:07,021 INFO  [org.jboss.as.server.deployment] (MSC service thread 1-3) JBAS015877: Stopped deployment snaLight.war in 884ms
10:22:07,021 INFO  [org.jboss.as.controller] (DeploymentScanner-threads - 1) JBAS014774: Service status report
JBAS014777:   Services which failed to start:      service jboss.deployment.unit."snaLight.war".PARSE: org.jboss.msc.service.StartException in service jboss.deployment.unit."snaLight.war".PARSE: Failed to process phase PARSE of deployment "snaLight.war"
 
10:22:07,021 ERROR [org.jboss.as.server.deployment.scanner] (DeploymentScanner-threads - 2) {"JBAS014653: Composite operation failed and was rolled back. Steps that failed:" => {"Operation step-2" => {"JBAS014671: Failed services" => {"jboss.deployment.unit.\"snaLight.war\".PARSE" => "org.jboss.msc.service.StartException in service jboss.deployment.unit.\"snaLight.war\".PARSE: Failed to process phase PARSE of deployment \"snaLight.war\""}}}}

原因是, 从jboss7.1开始会启动一个自己的webservice 服务, 如果部署的项目中使用了webservice_rt.jar包, 就会导致冲突. 修改方法是禁用jboss的这个webservice服务.

打开$JBOSS_HOME/standalone/configuration/standalone.xml, 注释下面几行

<extension module="org.jboss.as.webservices"/>
 
<subsystem xmlns="urn:jboss:domain:webservices:1.1">
    <modify-wsdl-address>true</modify-wsdl-address>
    <wsdl-host>${jboss.bind.address:127.0.0.1}</wsdl-host>
    <endpoint-config name="Standard-Endpoint-Config"/>
    <endpoint-config name="Recording-Endpoint-Config">
        <pre-handler-chain name="recording-handlers" protocol-bindings="##SOAP11_HTTP ##SOAP11_HTTP_MTOM ##SOAP12_HTTP ##SOAP12_HTTP_MTOM">
            <handler name="RecordingHandler" class="org.jboss.ws.common.invocation.RecordingServerHandler"/>
        </pre-handler-chain>
    </endpoint-config>
</subsystem>
jekyll实现页面键盘快捷键

在jekyll生成的文章页面加上了键盘快捷键导航, 左方向键浏览上一篇文章, 右方向键导航到下一篇.

$(function(){
  $(document).keydown(function(e) {
    var url = false;
    if (e.which == 37 || e.which == 72) {  // Left arrow and H
      
        url = '/blackberry_set_border_to_editfield/';
      
    } else if (e.which == 39 || e.which == 76) {  // Right arrow and L
      
        url = '/jboss_webservice_endpoint_already_registered/';
      
    }
    if (url) {
      window.location = url;
    }
  });
})