% Site Search % Michael Stone % February 20, 2012 Several folks have recently asked me how my "*Search site*" box works, so here's a quick writeup on the implementation as it stands at the time of this writing: 1. The current site is entirely static and is built by calling [`pandoc`] from a top-level non-recursive site [`Makefile`](/Makefile), written in [GNU Make][]. 2. The main inputs to this Makefile are "pages", which are represented as directories each containing a file named [`index.txt`](./index.txt) containing the [`pandoc` Markdown][] source for the page. 3. When run, my Makefile uses [`find`][] to locate page directories and then uses `pandoc` to convert the `index.txt` page sources into `index.html` outputs. Additionally, a tiny script named [`jsonify.py`](/jsonify.py), reproduced below: ~~~~ { .python } #!/usr/bin/env python from __future__ import with_statement import sys import cjson data = {} for fn in sys.argv[1:]: with open(fn) as f: data[fn.replace('index.txt', '')] = f.read() print cjson.encode(data) ~~~~ is used to build a target called [`site.json`](/site.json) containing a [JSON][]-encoded representation of all the source texts for all the pages in the site indexed by the name of the "page" directory that contains them. 4. Next, my Makefile uses `pandoc` with a custom [HTML template](/template.html) to produce HTML from my page sources that contains a text input field named `#searchbox` (initially hidden by CSS) and that runs the following [JavaScript][] program during page rendering: ~~~~ { .javascript } (function($){ $(document).ready(function(){ var site; $.getJSON('/site.json', function(data){ site = data; $("#searchbar").css("display", "inline-block"); }); var doSearch; doSearch = function(){ var search; search = $("#searchbox").val(); if (search.length > 0) { var results, pat; pat = RegExp(search); results = []; $.each(site, function(k, v){ if (v.match(pat)) { results.push('
  • ' + v.split(/\n/)[0].replace(/^% /, '') + '
  • '); } }); if (results.length > 0) { $("#searchresults").html('

    Matching Posts

    '); } else { $("#searchresults").html('

    Matching Posts

    None

    '); } } else { $("#searchresults").empty(); } }; $("#searchbox").keyup(doSearch); });})(jQuery); ~~~~ 5. When run, this script uses the [jQuery][] library to asynchronously fetch `site.json`, to unhide the searchbox on success, and to wait for jQuery [`keyup`] events. `keyup` events are then handled by selecting keys from the previously fetched `site` JavaScript object whose values are matched by the `pat` [JavaScript regexp][] (which was, in turn, built from the value of the `#searchbox` text field). Finally, the matching object keys are transformed into links and added to the `#searchresults` element's `innerHTML`. [`pandoc`]: http://johnmacfarlane.net/pandoc/ [`pandoc` Markdown]: http://johnmacfarlane.net/pandoc/README.html#pandocs-markdown [GNU Make]: http://www.gnu.org/software/make/manual/make.html [`find`]: http://www.gnu.org/software/findutils/manual/html_mono/find.html [jQuery]: http://docs.jquery.com/Main_Page [`keyup`]: http://api.jquery.com/keyup/ [JavaScript regexp]: http://www.regular-expressions.info/javascript.html [JSON]: http://json.org [JavaScript]: http://en.wikipedia.org/wiki/JavaScript