Jekyll for static site, KB with search engine (JSON and lunr.js based)

Jekyll

Lunr.js

Well, the idea of search across static content is super trivial and based on before-hand generated feed.xml. That xml file is generated by Jekyll when you say jekyll build .

Here is that JS search snippet:

    
(function () {
  var searchFile = '/feed.xml',
    searchEl,
    searchInputEl,
    searchResultsEl,
    currentInputValue = '',
    lastSearchResultHash,
    posts = [];

  // Changes XML to JSON
  // Modified version from here: http://davidwalsh.name/convert-xml-json
  function xmlToJson(xml) {
    // Create the return object
    var obj = {};
    if (xml.nodeType == 3) { // text
        obj = xml.nodeValue;
    }

    // do children
    // If all text nodes inside, get concatenated text from them.
    var textNodes = [].slice.call(xml.childNodes).filter(function (node) { return node.nodeType === 3; });
    if (xml.hasChildNodes() && xml.childNodes.length === textNodes.length) {
        obj = [].slice.call(xml.childNodes).reduce(function (text, node) { return text + node.nodeValue; }, '');
    }
    else if (xml.hasChildNodes()) {
        for(var i = 0; i < xml.childNodes.length; i++) {
            var item = xml.childNodes.item(i);
            var nodeName = item.nodeName;
            if (typeof(obj[nodeName]) == "undefined") {
                obj[nodeName] = xmlToJson(item);
            } else {
                if (typeof(obj[nodeName].push) == "undefined") {
                    var old = obj[nodeName];
                    obj[nodeName] = [];
                    obj[nodeName].push(old);
                }
                obj[nodeName].push(xmlToJson(item));
            }
        }
    }
    return obj;
  }

  function getPostsFromXml(xml) {
    var json = xmlToJson(xml);
    return json.channel.item;
  }

  window.toggleSearch = function toggleSearch() {
    searchEl.classList.toggle('is-active');
    if (searchEl.classList.contains('is-active')) {
        // while opening
        searchInputEl.value = '';
    } else {
        // while closing
        searchResultsEl.classList.add('is-hidden');
    }
    setTimeout(function () {
        searchInputEl.focus();
    }, 210);
  }

  function handleInput() {
    var currentResultHash, d;

    currentInputValue = (searchInputEl.value + '').toLowerCase();
    if (!currentInputValue || currentInputValue.length < 3) {
        lastSearchResultHash = '';
        searchResultsEl.classList.add('is-hidden');
        return;
    }
    searchResultsEl.style.offsetWidth;

    var matchingPosts = posts.filter(function (post) {
        if ((post.title + '').toLowerCase().indexOf(currentInputValue) !== -1 || (post.description + '').toLowerCase().indexOf(currentInputValue) !== -1) {
            return true;
        }
    });
    if (!matchingPosts.length) {
        searchResultsEl.classList.add('is-hidden');
    }
    currentResultHash = matchingPosts.reduce(function(hash, post) { return post.title + hash; }, '');
    if (matchingPosts.length && currentResultHash !== lastSearchResultHash) {
        searchResultsEl.classList.remove('is-hidden');
        searchResultsEl.innerHTML = matchingPosts.map(function (post) {
            // d = new Date(post.pubDate);
            return '<li><a href="' + post.link + '">' + post.title + '</a></li>';
        }).join('');
    }
    lastSearchResultHash = currentResultHash;
  }

  function init(options) {
    searchFile = options.searchFile || searchFile;
    searchEl = document.querySelector(options.searchSelector || '#js-super-search');
    searchInputEl = document.querySelector(options.inputSelector || '#js-super-search__input');
    searchResultsEl = document.querySelector(options.resultsSelector || '#js-super-search__results');

    var xmlhttp=new XMLHttpRequest();
    xmlhttp.open('GET', searchFile);
    xmlhttp.onreadystatechange = function () {
        if (xmlhttp.readyState != 4) return;
        if (xmlhttp.status != 200 && xmlhttp.status != 304) { return; }
        var node = (new DOMParser).parseFromString(xmlhttp.responseText, 'text/xml');
        node = node.children[0];
        posts = getPostsFromXml(node);
    }
    xmlhttp.send();

    searchInputEl.addEventListener('input', function onInputChange() {
        handleInput();
    });
  }

  init.toggle = toggleSearch;

  window.superSearch = init;

  })();
    

And that's the HTML layout where we search and append results.


<div class="super-search" id="js-super-search">
  <i class="material-icons prefix white-text">search</i>
  <input autofocus type="text" id="js-super-search__input" class="super-search__input validate">
  <label for="text">What to search?</label>
  <ul class="super-search__results" id="js-super-search__results"></ul>
</div>
    

In short, this is about:
#jekyll
#static site

Start discussion:
Related articles:
64 jekyll deployment orchestration preview
Automate your jekyll deployment with simple bash script (add, commit, push, deploy to AWS S3) ... more
117 seo friendly aws static website with ssl preview
It's been written many times, however there is no consistency how to make static web site with SSL and SEO friendly urls (301 redirect). Pure AWS based services static website ahead, check details inside! ... more