ghost-search: Our First Open Source Library for Ghost

After a bored day, a month ago, I decided to work on something new that might help others too. So I started working on ghost-search. Why? I felt that there is no search library for Ghost that takes advantage of their API. Hence ghost-search.

You can search through any Ghost resource (posts, tags, users) and use most of their parameters (fields, filter, include, order, formats, limit).

ghost-search is using as a search algorithm fuzzysort. Why not lunr or fuse you may ask? Well, the answer is simple. I wanted to use a search that returns relevant results to my query. After some heavy tests with lunr, fuse and other libraries, I was unhappy with the results that I've got. So thank you farzher for creating this simple and usable search library.

Read more here if you want to know how to setup and customize ghost-search.

Enough boring stuff, let's start with examples:

How to:

more examples in the future.

<input id="my-custom-input">
<div id="my-custom-results"></div>

<script type="text/javascript">
    let ghostSearch = new GhostSearch({
        results: '#my-custom-results'
    })
</script>

Display a message if there are no posts found

<input id="my-custom-input">
<div id="my-custom-results"></div>

<script type="text/javascript">
    let ghostSearch = new GhostSearch({
        input:   '#my-custom-input',
        results: '#my-custom-results',
        on: {
            afterDisplay: function(results){
                if (results.total == 0 && document.getElementById('my-custom-input').value != '') {
                    let resultsElement = document.getElementById('my-custom-results');
                    let e = document.createElement('p');
                    e.innerHTML = 'No results';
                    resultsElement.appendChild(e.firstChild);
                };
            }
        }
    })
</script>

Search only through tags

<input id="my-custom-input">
<div id="my-custom-results"></div>

<script type="text/javascript">
    let ghostSearch = new GhostSearch({
        input:   '#my-custom-input',
        results: '#my-custom-results',
        options: {
            keys: [
                'name',
            ],
        },
        api: {
            resource: 'tags',
            parameters: { 
                fields: ['name', 'slug'],
            },
        },
        template: function(result) {
            let url = [location.protocol, '//', location.host].join('') + '/tag';
            return '<a href="' + url + '/' + result.slug + '/">' + result.name + '</a>';  
        }
    })
</script>

Search posts from a custom collection


Let's say we have a routes.yaml like this:

routes:

collections:
  /themes/:
    permalink: /themes/{slug}/
    filter: tag:themes
    data: tag.themes
  /:
    permalink: /{slug}/
    filter: tag:-themes
    template:
      - index

taxonomies:
  tag: /tag/{slug}/
  author: /author/{slug}/

/themes/ is a collection that will show posts with tag themes. A post like this will have the url example.com/themes/post-slug.

Our ghost-search will become:

<input id="my-custom-input">
<div id="my-custom-results"></div>

<script type="text/javascript">
    let ghostSearch = new GhostSearch({
        input:   '#my-custom-input',
        results: '#my-custom-results',
	    options: {
	        keys: [
	            'title',
	        ],
	    },
	    api: {
	        resource: 'posts',
	        parameters: { 
	            fields: ['title', 'slug'],
	            filter: 'tags:[themes]',
	            include: 'tags'
	        },
	    },
	    template: function(result) {
	        let collection = 'themes';
	        let url = [location.protocol, '//', location.host].join('') + '/' + collection;
	        return '<a href="' + url + '/' + result.slug + '/">' + result.title + '</a>';  
	    },
    })
</script>

Unfortunately our theme doesn't have any custom collections yet. So we can't put a live example for this.

Search posts that are published after 01 Jan 2018

<input id="my-custom-input">
<div id="my-custom-results"></div>

<script type="text/javascript">
    let ghostSearch = new GhostSearch({
        input:   '#my-custom-input',
        results: '#my-custom-results',
        api: {
            parameters: { 
                filter: "published_at:>'2018-01-01 00:00:00'",
            },
        }
    })
</script>

Search through the title and content of posts

<input id="my-custom-input">
<div id="my-custom-results"></div>

<script type="text/javascript">
    let ghostSearch = new GhostSearch({
        input:   '#my-custom-input',
        results: '#my-custom-results',
        options: {
            keys: [
                'title',
                'plaintext'
            ],
        },
        api: {
            resource: 'posts',
            parameters: { 
                fields: ['title', 'slug', 'plaintext'],
                formats: 'plaintext',
            },
        },
    })
</script>

Get the results when a button is clicked

<form>
    <div id="my-custom-input"></div>
    <input type="submit" id="my-custom-button">
</form>
<div id="my-custom-results"></div>

<script type="text/javascript">
    let ghostSearch = new GhostSearch({
        input: '#my-custom-input',
        results: '#my-custom-results',
        button: '#my-custom-button'
    })
</script>

Get proper results when your Ghost is on a sub-path

<input id="my-custom-input">
<div id="my-custom-results"></div>

<script type="text/javascript">
    let ghostSearch = new GhostSearch({
        input: '#my-custom-input',
        results: '#my-custom-results',
        template: function(result) {
            let url = [location.protocol, '//', location.host].join('') + '/sub-path/';
            return '<a href="' + url + '/' + result.slug + '">' + result.title + '</a>';  
        }
    })
</script>

Set multiple instances of ghost-search on the same page

<input id="my-custom-input-1">
<div id="my-custom-results-1"></div>

<input id="my-custom-input-2">
<div id="my-custom-results-2"></div>

<script type="text/javascript">
    let ghostSearch1 = new GhostSearch({
    	input: 	 '#my-custom-input-1',
        results: '#my-custom-results-1'
    })
    let ghostSearch2 = new GhostSearch({
    	input: 	 '#my-custom-input-2',
        results: '#my-custom-results-2'
    })
</script>

Add a loading icon when you have a lot of posts

<input id="my-custom-input">
<div id="my-custom-results"></div>

<script type="text/javascript">
    let ghostSearch = new GhostSearch({
        input:   '#my-custom-input',
        results: '#my-custom-results',
        on: {
            beforeFetch: function(){
                let resultsElement = document.getElementById('my-custom-results');
                let e = document.createElement('div');
                e.innerHTML = '<img src="https://loading.io/spinners/spinner/index.ajax-spinner-preloader.svg">';
                resultsElement.appendChild(e.firstChild);
            },
            afterFetch: function(results){
                let resultsElement = document.getElementById('my-custom-results');
                resultsElement.innerHTML = '';
            }
        }
    })
</script>

Limit the results displayed

<input id="my-custom-input">
<div id="my-custom-results"></div>

<script type="text/javascript">
    let ghostSearch = new GhostSearch({
        input:   '#my-custom-input',
        results: '#my-custom-results',
        options: {
            limit: 3
        }
    })
</script>

more examples in the future.

I want more

Are you interested in a specific Ghost issue, feature or tutorial and we didn't cover it up in our blog? Drop us a message and we might write about it.