Jekyll search plugin based on lunr.js and jekyll_pages_api
Ruby CSS JavaScript HTML
Fetching latest commit…
Cannot retrieve the latest commit at this time.

README.md

jekyll_pages_api_search Plugin

Build Status

The jekyll_pages_api_search Ruby gem adds a lunr.js search index to a Jekyll-based web site.

The search index is generated and compressed automatically via jekyll build or jekyll serve. The supporting JavaScript code is optimized, compressed, and loads asynchronously. These features ensure that the preparation of the search index does not introduce rendering latency in the browser.

Search demo


Example of querying the search index and selecting from the search results page. The `/` key moves focus to the search query box, and the first result receives the tab index, making mouse-based navigation unnecessary.

How it works

On the server building the site, the plugin takes the corpus produced by the jekyll_pages_api gem and feeds it to a Node.js script that compiles it into a lunr.js index, serialized as JSON. The plugin adds this output file to the Jekyll site output. It generates a compressed copy as well, enabling web servers to take advantage of sending the compressed output directly, e.g. using the Nginx gzip_static on directive.

The gem also generates a (configurable, customizable) search results page that will serve the search results.

On the client, the search interface box submits a query form that fetches the search results page. The code on the search results page then fetches the search index from the server (which will be cached by the browser until the next site build). After loading the index, the page issues the query and inserts the results into its search results interface element.

All of the client-side components are bundled together with assets/js/search.js into assets/js/search-bundle.js using Browserify.

Installation

  1. Install Node.js on your system. This plugin requires version 4.2 or greater or version 5 or greater. You may wish to first install a version manager such as nvm to manage and install different Node.js versions.

  2. Add this line to your Jekyll project's Gemfile:

    group :jekyll_plugins do
      gem 'jekyll_pages_api_search'
    end
  3. Add the following to the project's _config.yml file:

    # Configuration for jekyll_pages_api_search plugin gem.
    jekyll_pages_api_search:
      # Uncomment this to speed up site generation while developing.
      #skip_index: true
    
      # Each member of `index_fields` should correspond to a field generated by
      # the jekyll_pages_api. It can hold an optional `boost` member as a signal
      # to Lunr.js to weight the field more highly (default is 1).
      index_fields:
        title:
          boost: 10
        tags:
          boost: 10
        url:
          boost: 5
        body:
    
      # If defined and browserify and uglifyify are installed, the plugin will
      # generate a bundle to define the renderJekyllPagesApiSearchResults
      # function.
      browserify:
        source: js/my-search.js
        target: js/my-search-bundle.js
  4. Run jekyll build or jekyll serve to produce search-index.json and search-index.json.gz files in the _site directory (or other output directory, as configured).

    If browserify: is defined, it will also produce the target: bundle file and its gzipped version. See the browserify section for more details.

  5. If you're running Nginx, you may want to use the gzip_static on directive to take advantage of the gzipped versions of the search index and supporting JavaScript.

Usage

To add the index to your pages, insert the following tags in your _layouts and _includes files as you see fit:

  • {% jekyll_pages_api_search_interface %}: inserts the HTML for the search box and search results
  • {% jekyll_pages_api_search_load %}: inserts the <script> tags to load the search code asynchronously; the search code will then load search-index.json asynchronously

You can also add @import "jekyll_pages_api_search"; to one of your Sass assets to use the default interface style.

Add skip_index: true to the front matter of any documents you would like to exclude from the index (e.g. indexes that contain summaries of other documents).

Configuration

In addition to the fields listed above in the installation instructions, the following properties of the jekyll_pages_api_search configuration object are also available:

  • results_page_title: Sets the title of the generated search results page.
  • layout: The name of the layout file for the search results page, minus the .html suffix. By default this is search-results, and is provided by the plugin. If you add a search-results.html to your site's layout_dir (typically _layouts), the plugin will use that layout instead.
  • endpoint: The name of the endpoint generated for the search results page. Defaults to search.

Results UI and search engine options

To customize elements of the search user interface and search engine, add the following objects to your search result page's layout before the {% jekyll_pages_api_search_load %} tag. Override only the properties required for your installation (default values shown below).

<script>
var JEKYLL_PAGES_API_SEARCH_UI_OPTIONS = {
  // ID of the search input element
  inputElementId: 'search-input',

  // ID of the search results element; the search will only run on the page or
  // pages that contain an element matching this ID
  searchResultsId: 'search-results',

  // Prefix of the message generated to indicate an empty result set; the
  // query string will be appended
  emptyResultsMessagePrefix: 'No results found for',

  // Type of HTML element that will contain the empty results message; will be
  // added to the parent of the searchResultsId element, before the
  // searchResultsId element
  emptyResultsElementType: 'p',

  // CSS class assigned to the empty results message element
  emptyResultsElementClass: 'search-empty'
};

var JEKYLL_PAGES_API_SEARCH_ENGINE_OPTIONS = {
  // Path to the generated Lunr.js search index relative to site.baseurl;
  // should match what is generated by the Ruby class
  // JekyllPagesApiSearch::SearchIndexBuilder.
  indexPath: '/search-index.json',

  // URL query string parameter containing the search query string
  queryParam: 'q'
};
</script>

To override the default search results rendering function, define a function called renderJekyllPagesApiSearchResults that conforms to the following interface. This is the default implementation, which creates new <li> elements containing a link for each search result.

<script>
/*
 * Params:
 * - query: query string
 * - results: search results matching the query
 * - doc: window.document object
 * - resultsElem: HTML element to which generated search results elements will
 *     be appended
 */
function renderJekyllPagesApiSearchResults(query, results, doc, resultsElem) {
  results.forEach(function(result, index) {
    var item = doc.createElement('li'),
        link = doc.createElement('a'),
        text = doc.createTextNode(result.title);

    link.appendChild(text);
    link.title = result.title;
    link.href = result.url;
    item.appendChild(link);
    resultsElem.appendChild(item);

    link.tabindex = index;
    if (index === 0) {
      link.focus();
    }
  });
}
</script>

Using browserify

The most modular means of defining renderJekyllPagesApiSearchResults may be to create a Node.js implementation file and generate a browser-compatible version using browserify. First create the implementation as described above. Then perform the following steps:

# Create a package.json file
$ npm init

# Install browserify and uglifyify, a JavaScript minimizer
$ npm install browserify uglifyify --save-dev

Add the browserify: configuration as defined in the installation instructions above, replacing js/my-search.js with the path to your renderJekyllPagesApiSearchResults implementation script and js/my-search-bundle.js with the path to your generated bundle.

Examples from apps.gov

apps.gov's default layout contains an example of setting the user interface options in concert with a custom search results rendering script:

  </body>
<script>
var JEKYLL_PAGES_API_SEARCH_UI_OPTIONS = {
  inputElementId: 'search-field',
  searchResultsId: 'product-list',
  emptyResultsMessagePrefix: 'No products found matching'
};
</script>
{% jekyll_pages_api_search_load %}
</html>

Note that if you have a different input element on different pages, you can add something similar to the following to each corresponding layout (taken from apps.gov's homepage layout):

  </body>
<script>
var JEKYLL_PAGES_API_SEARCH_UI_OPTIONS = {
  inputElementId: 'search-field-big'
};
</script>
{% jekyll_pages_api_search_load %}
</html>

Customizing tags and script loading

If you prefer to craft your own versions of these tags and styles, you can capture the output of these tags and the Sass @import statement, then create new tags or included files based on this output, careful not to change anything that causes the interaction between these components to fail.

Alternately, you can inspect the code of this gem (all paths relative to lib/jekyll_pages_api_search/):

  • {% jekyll_pages_api_search_interface %}: includes search.html
  • {% jekyll_pages_api_search_load %}: generated by the LoadSearchTag class from tags.rb
  • {% jekyll_pages_api_search_results %}: generated by the SearchResultsTag class from tags.rb
  • @import "jekyll_pages_api_search";: includes sass/jekyll_pages_api_search.scss

Running standalone

If you wish to generate a search-index.json file (and optionaly a pages.json file) when using a site generation tool other than Jekyll, you can run the jekyll_pages_api_search executable as a post-generation step. Run jekyll_pages_api -h for instructions.

Developing

Install Node.js per the installation instructions (step #1). The Rakefile will prompt you to install Node.js and any packages that are missing from your system when running bundle exec rake build.

After cloning this repository, do the following to ensure your installation is in a good state:

$ cd jekyll_pages_api_search
$ npm install
$ bundle install
$ bundle exec rake test

Run bundle exec rake -T to get a list of build commands and descriptions.

Commit an update to bump the version number of lib/jekyll_pages_api_search/version.rb before running bundle exec rake release.

While developing this gem, add this to the Gemfile of any project using the gem to try out your changes (presuming the project's working directory is a sibling of the gem's working directory):

group :jekyll_plugins do
  gem 'jekyll_pages_api_search', :path => '../jekyll_pages_api_search'
end

Releasing

After following the steps from the Developing section to build and test the gem:

  1. Ensure all changes for the release have already been merged all into the master branch.

  2. Bump the version number by editing lib/jekyll_pages_api_search/version.rb.

  3. Commit the version number update directly to the master branch, replacing X.X.X with the new version number:

    $ git commit -m 'Bump to vX.X.X' lib/jekyll_pages_api_search/version.rb
  4. Finally, run the following command. It will build the gem, tag the head commit in git, push the branch and tag to GitHub, and ultimately push the release to jekyll_pages_api_search on RubyGems.org.

    $ bundle exec rake release

Contributing

If you'd like to contribute to this repository, please follow our CONTRIBUTING guidelines.

Public domain

This project is in the worldwide public domain. As stated in CONTRIBUTING:

This project is in the public domain within the United States, and copyright and related rights in the work worldwide are waived through the CC0 1.0 Universal public domain dedication.

All contributions to this project will be released under the CC0 dedication. By submitting a pull request, you are agreeing to comply with this waiver of copyright interest.