diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..b512c09
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+node_modules
\ No newline at end of file
diff --git a/README.md b/README.md
index 3cf797b..6aaf795 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,401 @@
# ghost-search
-A simple but powerful search library for Ghost Blogging Platform.
+
+A simple but powerful search library for [Ghost Blogging Platform](https://ghost.org/).
+
+## Setup
+
+Open your theme directory and navigate to `assets` subdirectory. \
+Create a directory called `js`, if there isn't already one in there, and add the minified version of `ghost-search` in it. \
+Open `default.hbs` that is located at the root of your theme. \
+At the bottom of this file you should see `{{ghost_foot}}`. \
+Add the following code above it and save it:
+```
+
+```
+
+Add the following code, in a `.hbs` file, where you want to show the search input:
+
+```html
+
+```
+
+Add the following code, in a `.hbs` file, where you want to show the search results:
+
+```
+
+```
+
+You will need to initialize `ghost-search` to make the search functional. \
+Add the following js code after you included `ghost-search.min.js`:
+
+```html
+
+```
+
+## Live Examples
+
+[Set a basic search](https://www.hauntedthemes.com/ghost-search-our-first-open-source-library-for-ghost/#set-a-basic-search) \
+[Display a message if there are no posts found](https://www.hauntedthemes.com/ghost-search-our-first-open-source-library-for-ghost/#display-a-message-if-there-are-no-posts-found) \
+[Search only through tags](https://www.hauntedthemes.com/ghost-search-our-first-open-source-library-for-ghost/#search-only-through-tags) \
+[Search posts from a custom collection](https://www.hauntedthemes.com/ghost-search-our-first-open-source-library-for-ghost/#search-posts-from-a-custom-collection) \
+[Search posts that are published after 01 Jan 2018](https://www.hauntedthemes.com/ghost-search-our-first-open-source-library-for-ghost/#search-posts-that-are-published-after-01-jan-2018) \
+[Search through the title and content of posts](https://www.hauntedthemes.com/ghost-search-our-first-open-source-library-for-ghost/#search-through-the-title-and-content-of-posts) \
+[Get the results when a button is clicked](https://www.hauntedthemes.com/ghost-search-our-first-open-source-library-for-ghost/#get-the-results-when-a-button-is-clicked) \
+[Get proper results when your Ghost is on a sub-path](https://www.hauntedthemes.com/ghost-search-our-first-open-source-library-for-ghost/#get-proper-results-when-your-ghost-is-on-a-sub-path) \
+[Set multiple instances of ghost-search on the same page](https://www.hauntedthemes.com/ghost-search-our-first-open-source-library-for-ghost/#set-multiple-instances-of-ghost-search-on-the-same-page) \
+[Add a loading icon when you have a lot of posts](https://www.hauntedthemes.com/ghost-search-our-first-open-source-library-for-ghost/#add-a-loading-icon-when-you-have-a-lot-of-posts) \
+[Limit the results displayed](https://www.hauntedthemes.com/ghost-search-our-first-open-source-library-for-ghost/#limit-the-results-displayed)
+
+## Default Options
+
+```
+{
+ input: '#ghost-search-field',
+ results: '#ghost-search-results',
+ button: '',
+ development: false,
+ template: function(result) {
+ let url = [location.protocol, '//', location.host].join('');
+ return '' + result.title + '';
+ },
+ trigger: 'focus',
+ options: {
+ keys: [
+ 'title'
+ ],
+ limit: 100,
+ threshold: -3500,
+ allowTypo: false
+ },
+ api: {
+ resource: 'posts',
+ parameters: {
+ limit: 'all',
+ fields: ['title', 'slug'],
+ filter: '',
+ include: '',
+ order: '',
+ formats: '',
+ },
+ },
+ on: {
+ beforeDisplay: function(){},
+ afterDisplay: function(results){},
+ beforeFetch: function(){},
+ afterFetch: function(){}
+ }
+}
+```
+
+## Options
+
+### input
+
+The ID of the input field that will be transformed into search filter. \
+You can set your own id if you want like this:
+
+```html
+
+```
+
+```html
+
+```
+
+Default value: `'#ghost-search-field'`
+
+### results
+
+The ID of the element that will be transformed into search results. \
+You can set your own id if you want like this:
+
+```html
+
+```
+
+```html
+
+```
+
+Default value: `'#ghost-search-results'`
+
+### button
+
+The ID of the element that will trigger the search results after it's clicked. \
+By default, the button parameter is empty because `ghost-search` displays the results when you write in the input. \
+To make this work you need to add the input and the button in a `form` element:
+
+```html
+
+
+```
+
+```html
+
+```
+
+Default value: `''`
+
+### development
+
+A parameter that will do some validation to your current instance. Might be useful if you use the `api` parameter. \
+By default it is set to `false` but you can set it like this to `true`:
+
+```html
+
+```
+
+Default value: `false`
+
+### template
+
+The template that will be used to render individual items in the search result. \
+The method has a parameter `result` that stores all the data that you can use inside the method.
+
+#### Examples:
+
+Make the results a list and wrap each result with `
`:
+
+```html
+
+
+```
+
+```html
+
+```
+
+Set url with sub-path:
+
+```html
+
+```
+
+Default value:
+
+```html
+function(result) {
+ let url = [location.protocol, '//', location.host].join('');
+ return '' + result.title + '';
+}
+```
+
+### trigger
+
+Tells the script when to fetch the collection of data. The default value is `focus`, that means when a user clicks the input, all the data is fetched. \
+You can also use `load`. This will fetch the data when the page loads.
+
+`load` might **create a DDOS effect** because it loads all the data every time a page loads. Use carefully.
+
+Default value: `'focus'`
+
+### options
+
+`ghost-search` is using `fuzzysort` as an algorithm for search. The `option` parameter supports all the options from [fuzzysort](https://github.com/farzher/fuzzysort#options).
+By default, `ghost-search` is showing the first 10 results and searches only based on title.
+
+Let's try another example that will show the first 3 results and searches both title and the content of a collection:
+
+```html
+
+```
+
+Default value:
+```
+{
+ keys: [
+ 'title'
+ ],
+ limit: 10,
+ threshold: -3500,
+ allowTypo: false
+}
+```
+
+### api
+
+The api parameter is an object that supports most of the [resources](https://api.ghost.org/docs/post) and [parameters](https://api.ghost.org/docs/limit) by [Ghost API](https://api.ghost.org).
+
+Resources: [posts](https://api.ghost.org/docs/post), [tags](https://api.ghost.org/docs/tag), [users](https://api.ghost.org/docs/user) \
+Parameters: [fields](https://api.ghost.org/docs/fields), [filter](https://api.ghost.org/docs/filter), [include](https://api.ghost.org/docs/include), [order](https://api.ghost.org/docs/order), [formats](https://api.ghost.org/docs/formats), [limit](https://api.ghost.org/docs/limit)
+
+Examples:
+
+Search through tags:
+
+```html
+
+```
+
+Search through 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:
+
+```
+let ghostSearch = new GhostSearch({
+ 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 '' + result.title + '';
+ },
+})
+```
+
+### on
+
+This parameter has 4 methods in it: `beforeDisplay`, `afterDisplay`, `beforeFetch`, `afterFetch`. \
+`afterDisplay` and `afterFetch` have a parameter `results` that contains the results fetched. \
+They are usefull to do things before results are visible to users.
+
+Example:
+
+```
+let ghostSearch = new GhostSearch({
+ on: {
+ beforeFetch: function(){
+ // Create a div that has a spinning icon
+ console.log('Loading appers');
+ },
+ afterFetch: function(results){
+ // Remove the spinning icon
+ console.log('Loading disappears');
+ }
+ }
+})
+```
+
+## Contributing
+
+All changes should be committed to `src/` files only.
+
+## Known Issues
+* DDOS effect when `trigger` is set to `load`
+If you have a lot of posts and set `trigger` to `load` you might get a DDOS effect because you are loading all the post everything a page loads. It would be better to just set `trigger` to `focus`.
+* [Avoid using `fields` if you are filtering on a relationship](https://github.com/TryGhost/Ghost/issues/8649).
+If you include `tags` or `authors`, the library removes everything you have in `fields` parameter.
+
+## Changelog
+
+### 0.1.0 - 17 Sep 2018
+* Initial release
+
+## Thank You
+ghost-search is using as a search algorithm [fuzzysort](https://github.com/farzher/fuzzysort). \
+Thank you [farzher](https://github.com/farzher/) for creating fuzzysort, a simple and usable search library.
+
+## Copyright & License
+
+Copyright (c) 2018 Haunted Themes - Released under the [MIT license](LICENSE).
+[Ghost is a trademark of The Ghost Foundation](https://ghost.org/)
\ No newline at end of file
diff --git a/dist/ghost-search.js b/dist/ghost-search.js
new file mode 100644
index 0000000..78f2b08
--- /dev/null
+++ b/dist/ghost-search.js
@@ -0,0 +1,935 @@
+/**
+ * ghost-search 0.1.0 (https://github.com/HauntedThemes/ghost-search)
+ * A simple but powerful search library for Ghost Blogging Platform.
+ * Copyright 2018 Haunted Themes (https://www.hauntedthemes.com)
+ * Released under MIT License
+ * Released on: 17 Sep 2018
+ */
+
+/*
+WHAT: SublimeText-like Fuzzy Search
+
+USAGE:
+ fuzzysort.single('fs', 'Fuzzy Search') // {score: -16}
+ fuzzysort.single('test', 'test') // {score: 0}
+ fuzzysort.single('doesnt exist', 'target') // null
+
+ fuzzysort.go('mr', ['Monitor.cpp', 'MeshRenderer.cpp'])
+ // [{score: -18, target: "MeshRenderer.cpp"}, {score: -6009, target: "Monitor.cpp"}]
+
+ fuzzysort.highlight(fuzzysort.single('fs', 'Fuzzy Search'), '', '')
+ // Fuzzy Search
+*/
+
+// UMD (Universal Module Definition) for fuzzysort
+;(function(root, UMD) {
+ if(typeof define === 'function' && define.amd) define([], UMD)
+ else if(typeof module === 'object' && module.exports) module.exports = UMD()
+ else root.fuzzysort = UMD()
+})(this, function UMD() { function fuzzysortNew(instanceOptions) {
+
+ var fuzzysort = {
+
+ single: function(search, target, options) {
+ if(!search) return null
+ if(!isObj(search)) search = fuzzysort.getPreparedSearch(search)
+
+ if(!target) return null
+ if(!isObj(target)) target = fuzzysort.getPrepared(target)
+
+ var allowTypo = options && options.allowTypo!==undefined ? options.allowTypo
+ : instanceOptions && instanceOptions.allowTypo!==undefined ? instanceOptions.allowTypo
+ : true
+ var algorithm = allowTypo ? fuzzysort.algorithm : fuzzysort.algorithmNoTypo
+ return algorithm(search, target, search[0])
+ // var threshold = options && options.threshold || instanceOptions && instanceOptions.threshold || -9007199254740991
+ // var result = algorithm(search, target, search[0])
+ // if(result === null) return null
+ // if(result.score < threshold) return null
+ // return result
+ },
+
+ go: function(search, targets, options) {
+ if(!search) return noResults
+ search = fuzzysort.prepareSearch(search)
+ var searchLowerCode = search[0]
+
+ var threshold = options && options.threshold || instanceOptions && instanceOptions.threshold || -9007199254740991
+ var limit = options && options.limit || instanceOptions && instanceOptions.limit || 9007199254740991
+ var allowTypo = options && options.allowTypo!==undefined ? options.allowTypo
+ : instanceOptions && instanceOptions.allowTypo!==undefined ? instanceOptions.allowTypo
+ : true
+ var algorithm = allowTypo ? fuzzysort.algorithm : fuzzysort.algorithmNoTypo
+ var resultsLen = 0; var limitedCount = 0
+ var targetsLen = targets.length
+
+ // This code is copy/pasted 3 times for performance reasons [options.keys, options.key, no keys]
+
+ // options.keys
+ if(options && options.keys) {
+ var scoreFn = options.scoreFn || defaultScoreFn
+ var keys = options.keys
+ var keysLen = keys.length
+ for(var i = targetsLen - 1; i >= 0; --i) { var obj = targets[i]
+ var objResults = new Array(keysLen)
+ for (var keyI = keysLen - 1; keyI >= 0; --keyI) {
+ var key = keys[keyI]
+ var target = getValue(obj, key)
+ if(!target) { objResults[keyI] = null; continue }
+ if(!isObj(target)) target = fuzzysort.getPrepared(target)
+
+ objResults[keyI] = algorithm(search, target, searchLowerCode)
+ }
+ objResults.obj = obj // before scoreFn so scoreFn can use it
+ var score = scoreFn(objResults)
+ if(score === null) continue
+ if(score < threshold) continue
+ objResults.score = score
+ if(resultsLen < limit) { q.add(objResults); ++resultsLen }
+ else {
+ ++limitedCount
+ if(score > q.peek().score) q.replaceTop(objResults)
+ }
+ }
+
+ // options.key
+ } else if(options && options.key) {
+ var key = options.key
+ for(var i = targetsLen - 1; i >= 0; --i) { var obj = targets[i]
+ var target = getValue(obj, key)
+ if(!target) continue
+ if(!isObj(target)) target = fuzzysort.getPrepared(target)
+
+ var result = algorithm(search, target, searchLowerCode)
+ if(result === null) continue
+ if(result.score < threshold) continue
+
+ // have to clone result so duplicate targets from different obj can each reference the correct obj
+ result = {target:result.target, _targetLowerCodes:null, _nextBeginningIndexes:null, score:result.score, indexes:result.indexes, obj:obj} // hidden
+
+ if(resultsLen < limit) { q.add(result); ++resultsLen }
+ else {
+ ++limitedCount
+ if(result.score > q.peek().score) q.replaceTop(result)
+ }
+ }
+
+ // no keys
+ } else {
+ for(var i = targetsLen - 1; i >= 0; --i) { var target = targets[i]
+ if(!target) continue
+ if(!isObj(target)) target = fuzzysort.getPrepared(target)
+
+ var result = algorithm(search, target, searchLowerCode)
+ if(result === null) continue
+ if(result.score < threshold) continue
+ if(resultsLen < limit) { q.add(result); ++resultsLen }
+ else {
+ ++limitedCount
+ if(result.score > q.peek().score) q.replaceTop(result)
+ }
+ }
+ }
+
+ if(resultsLen === 0) return noResults
+ var results = new Array(resultsLen)
+ for(var i = resultsLen - 1; i >= 0; --i) results[i] = q.poll()
+ results.total = resultsLen + limitedCount
+ return results
+ },
+
+ goAsync: function(search, targets, options) {
+ var canceled = false
+ var p = new Promise(function(resolve, reject) {
+ if(!search) return resolve(noResults)
+ search = fuzzysort.prepareSearch(search)
+ var searchLowerCode = search[0]
+
+ var q = fastpriorityqueue()
+ var iCurrent = targets.length - 1
+ var threshold = options && options.threshold || instanceOptions && instanceOptions.threshold || -9007199254740991
+ var limit = options && options.limit || instanceOptions && instanceOptions.limit || 9007199254740991
+ var allowTypo = options && options.allowTypo!==undefined ? options.allowTypo
+ : instanceOptions && instanceOptions.allowTypo!==undefined ? instanceOptions.allowTypo
+ : true
+ var algorithm = allowTypo ? fuzzysort.algorithm : fuzzysort.algorithmNoTypo
+ var resultsLen = 0; var limitedCount = 0
+ function step() {
+ if(canceled) return reject('canceled')
+
+ var startMs = Date.now()
+
+ // This code is copy/pasted 3 times for performance reasons [options.keys, options.key, no keys]
+
+ // options.keys
+ if(options && options.keys) {
+ var scoreFn = options.scoreFn || defaultScoreFn
+ var keys = options.keys
+ var keysLen = keys.length
+ for(; iCurrent >= 0; --iCurrent) { var obj = targets[iCurrent]
+ var objResults = new Array(keysLen)
+ for (var keyI = keysLen - 1; keyI >= 0; --keyI) {
+ var key = keys[keyI]
+ var target = getValue(obj, key)
+ if(!target) { objResults[keyI] = null; continue }
+ if(!isObj(target)) target = fuzzysort.getPrepared(target)
+
+ objResults[keyI] = algorithm(search, target, searchLowerCode)
+ }
+ objResults.obj = obj // before scoreFn so scoreFn can use it
+ var score = scoreFn(objResults)
+ if(score === null) continue
+ if(score < threshold) continue
+ objResults.score = score
+ if(resultsLen < limit) { q.add(objResults); ++resultsLen }
+ else {
+ ++limitedCount
+ if(score > q.peek().score) q.replaceTop(objResults)
+ }
+
+ if(iCurrent%1000/*itemsPerCheck*/ === 0) {
+ if(Date.now() - startMs >= 10/*asyncInterval*/) {
+ isNode?setImmediate(step):setTimeout(step)
+ return
+ }
+ }
+ }
+
+ // options.key
+ } else if(options && options.key) {
+ var key = options.key
+ for(; iCurrent >= 0; --iCurrent) { var obj = targets[iCurrent]
+ var target = getValue(obj, key)
+ if(!target) continue
+ if(!isObj(target)) target = fuzzysort.getPrepared(target)
+
+ var result = algorithm(search, target, searchLowerCode)
+ if(result === null) continue
+ if(result.score < threshold) continue
+
+ // have to clone result so duplicate targets from different obj can each reference the correct obj
+ result = {target:result.target, _targetLowerCodes:null, _nextBeginningIndexes:null, score:result.score, indexes:result.indexes, obj:obj} // hidden
+
+ if(resultsLen < limit) { q.add(result); ++resultsLen }
+ else {
+ ++limitedCount
+ if(result.score > q.peek().score) q.replaceTop(result)
+ }
+
+ if(iCurrent%1000/*itemsPerCheck*/ === 0) {
+ if(Date.now() - startMs >= 10/*asyncInterval*/) {
+ isNode?setImmediate(step):setTimeout(step)
+ return
+ }
+ }
+ }
+
+ // no keys
+ } else {
+ for(; iCurrent >= 0; --iCurrent) { var target = targets[iCurrent]
+ if(!target) continue
+ if(!isObj(target)) target = fuzzysort.getPrepared(target)
+
+ var result = algorithm(search, target, searchLowerCode)
+ if(result === null) continue
+ if(result.score < threshold) continue
+ if(resultsLen < limit) { q.add(result); ++resultsLen }
+ else {
+ ++limitedCount
+ if(result.score > q.peek().score) q.replaceTop(result)
+ }
+
+ if(iCurrent%1000/*itemsPerCheck*/ === 0) {
+ if(Date.now() - startMs >= 10/*asyncInterval*/) {
+ isNode?setImmediate(step):setTimeout(step)
+ return
+ }
+ }
+ }
+ }
+
+ if(resultsLen === 0) return resolve(noResults)
+ var results = new Array(resultsLen)
+ for(var i = resultsLen - 1; i >= 0; --i) results[i] = q.poll()
+ results.total = resultsLen + limitedCount
+ resolve(results)
+ }
+
+ isNode?setImmediate(step):step()
+ })
+ p.cancel = function() { canceled = true }
+ return p
+ },
+
+ highlight: function(result, hOpen, hClose) {
+ if(result === null) return null
+ if(hOpen === undefined) hOpen = ''
+ if(hClose === undefined) hClose = ''
+ var highlighted = ''
+ var matchesIndex = 0
+ var opened = false
+ var target = result.target
+ var targetLen = target.length
+ var matchesBest = result.indexes
+ for(var i = 0; i < targetLen; ++i) { var char = target[i]
+ if(matchesBest[matchesIndex] === i) {
+ ++matchesIndex
+ if(!opened) { opened = true
+ highlighted += hOpen
+ }
+
+ if(matchesIndex === matchesBest.length) {
+ highlighted += char + hClose + target.substr(i+1)
+ break
+ }
+ } else {
+ if(opened) { opened = false
+ highlighted += hClose
+ }
+ }
+ highlighted += char
+ }
+
+ return highlighted
+ },
+
+ prepare: function(target) {
+ if(!target) return
+ return {target:target, _targetLowerCodes:fuzzysort.prepareLowerCodes(target), _nextBeginningIndexes:null, score:null, indexes:null, obj:null} // hidden
+ },
+ prepareSlow: function(target) {
+ if(!target) return
+ return {target:target, _targetLowerCodes:fuzzysort.prepareLowerCodes(target), _nextBeginningIndexes:fuzzysort.prepareNextBeginningIndexes(target), score:null, indexes:null, obj:null} // hidden
+ },
+ prepareSearch: function(search) {
+ if(!search) return
+ return fuzzysort.prepareLowerCodes(search)
+ },
+
+
+
+ // Below this point is only internal code
+ // Below this point is only internal code
+ // Below this point is only internal code
+ // Below this point is only internal code
+
+
+
+ getPrepared: function(target) {
+ if(target.length > 999) return fuzzysort.prepare(target) // don't cache huge targets
+ var targetPrepared = preparedCache.get(target)
+ if(targetPrepared !== undefined) return targetPrepared
+ targetPrepared = fuzzysort.prepare(target)
+ preparedCache.set(target, targetPrepared)
+ return targetPrepared
+ },
+ getPreparedSearch: function(search) {
+ if(search.length > 999) return fuzzysort.prepareSearch(search) // don't cache huge searches
+ var searchPrepared = preparedSearchCache.get(search)
+ if(searchPrepared !== undefined) return searchPrepared
+ searchPrepared = fuzzysort.prepareSearch(search)
+ preparedSearchCache.set(search, searchPrepared)
+ return searchPrepared
+ },
+
+ algorithm: function(searchLowerCodes, prepared, searchLowerCode) {
+ var targetLowerCodes = prepared._targetLowerCodes
+ var searchLen = searchLowerCodes.length
+ var targetLen = targetLowerCodes.length
+ var searchI = 0 // where we at
+ var targetI = 0 // where you at
+ var typoSimpleI = 0
+ var matchesSimpleLen = 0
+
+ // very basic fuzzy match; to remove non-matching targets ASAP!
+ // walk through target. find sequential matches.
+ // if all chars aren't found then exit
+ for(;;) {
+ var isMatch = searchLowerCode === targetLowerCodes[targetI]
+ if(isMatch) {
+ matchesSimple[matchesSimpleLen++] = targetI
+ ++searchI; if(searchI === searchLen) break
+ searchLowerCode = searchLowerCodes[typoSimpleI===0?searchI : (typoSimpleI===searchI?searchI+1 : (typoSimpleI===searchI-1?searchI-1 : searchI))]
+ }
+
+ ++targetI; if(targetI >= targetLen) { // Failed to find searchI
+ // Check for typo or exit
+ // we go as far as possible before trying to transpose
+ // then we transpose backwards until we reach the beginning
+ for(;;) {
+ if(searchI <= 1) return null // not allowed to transpose first char
+ if(typoSimpleI === 0) { // we haven't tried to transpose yet
+ --searchI
+ var searchLowerCodeNew = searchLowerCodes[searchI]
+ if(searchLowerCode === searchLowerCodeNew) continue // doesn't make sense to transpose a repeat char
+ typoSimpleI = searchI
+ } else {
+ if(typoSimpleI === 1) return null // reached the end of the line for transposing
+ --typoSimpleI
+ searchI = typoSimpleI
+ searchLowerCode = searchLowerCodes[searchI + 1]
+ var searchLowerCodeNew = searchLowerCodes[searchI]
+ if(searchLowerCode === searchLowerCodeNew) continue // doesn't make sense to transpose a repeat char
+ }
+ matchesSimpleLen = searchI
+ targetI = matchesSimple[matchesSimpleLen - 1] + 1
+ break
+ }
+ }
+ }
+
+ var searchI = 0
+ var typoStrictI = 0
+ var successStrict = false
+ var matchesStrictLen = 0
+
+ var nextBeginningIndexes = prepared._nextBeginningIndexes
+ if(nextBeginningIndexes === null) nextBeginningIndexes = prepared._nextBeginningIndexes = fuzzysort.prepareNextBeginningIndexes(prepared.target)
+ var firstPossibleI = targetI = matchesSimple[0]===0 ? 0 : nextBeginningIndexes[matchesSimple[0]-1]
+
+ // Our target string successfully matched all characters in sequence!
+ // Let's try a more advanced and strict test to improve the score
+ // only count it as a match if it's consecutive or a beginning character!
+ if(targetI !== targetLen) for(;;) {
+ if(targetI >= targetLen) {
+ // We failed to find a good spot for this search char, go back to the previous search char and force it forward
+ if(searchI <= 0) { // We failed to push chars forward for a better match
+ // transpose, starting from the beginning
+ ++typoStrictI; if(typoStrictI > searchLen-2) break
+ if(searchLowerCodes[typoStrictI] === searchLowerCodes[typoStrictI+1]) continue // doesn't make sense to transpose a repeat char
+ targetI = firstPossibleI
+ continue
+ }
+
+ --searchI
+ var lastMatch = matchesStrict[--matchesStrictLen]
+ targetI = nextBeginningIndexes[lastMatch]
+
+ } else {
+ var isMatch = searchLowerCodes[typoStrictI===0?searchI : (typoStrictI===searchI?searchI+1 : (typoStrictI===searchI-1?searchI-1 : searchI))] === targetLowerCodes[targetI]
+ if(isMatch) {
+ matchesStrict[matchesStrictLen++] = targetI
+ ++searchI; if(searchI === searchLen) { successStrict = true; break }
+ ++targetI
+ } else {
+ targetI = nextBeginningIndexes[targetI]
+ }
+ }
+ }
+
+ { // tally up the score & keep track of matches for highlighting later
+ if(successStrict) { var matchesBest = matchesStrict; var matchesBestLen = matchesStrictLen }
+ else { var matchesBest = matchesSimple; var matchesBestLen = matchesSimpleLen }
+ var score = 0
+ var lastTargetI = -1
+ for(var i = 0; i < searchLen; ++i) { var targetI = matchesBest[i]
+ // score only goes down if they're not consecutive
+ if(lastTargetI !== targetI - 1) score -= targetI
+ lastTargetI = targetI
+ }
+ if(!successStrict) {
+ score *= 1000
+ if(typoSimpleI !== 0) score += -20/*typoPenalty*/
+ } else {
+ if(typoStrictI !== 0) score += -20/*typoPenalty*/
+ }
+ score -= targetLen - searchLen
+ prepared.score = score
+ prepared.indexes = new Array(matchesBestLen); for(var i = matchesBestLen - 1; i >= 0; --i) prepared.indexes[i] = matchesBest[i]
+
+ return prepared
+ }
+ },
+
+ algorithmNoTypo: function(searchLowerCodes, prepared, searchLowerCode) {
+ var targetLowerCodes = prepared._targetLowerCodes
+ var searchLen = searchLowerCodes.length
+ var targetLen = targetLowerCodes.length
+ var searchI = 0 // where we at
+ var targetI = 0 // where you at
+ var matchesSimpleLen = 0
+
+ // very basic fuzzy match; to remove non-matching targets ASAP!
+ // walk through target. find sequential matches.
+ // if all chars aren't found then exit
+ for(;;) {
+ var isMatch = searchLowerCode === targetLowerCodes[targetI]
+ if(isMatch) {
+ matchesSimple[matchesSimpleLen++] = targetI
+ ++searchI; if(searchI === searchLen) break
+ searchLowerCode = searchLowerCodes[searchI]
+ }
+ ++targetI; if(targetI >= targetLen) return null // Failed to find searchI
+ }
+
+ var searchI = 0
+ var successStrict = false
+ var matchesStrictLen = 0
+
+ var nextBeginningIndexes = prepared._nextBeginningIndexes
+ if(nextBeginningIndexes === null) nextBeginningIndexes = prepared._nextBeginningIndexes = fuzzysort.prepareNextBeginningIndexes(prepared.target)
+ var firstPossibleI = targetI = matchesSimple[0]===0 ? 0 : nextBeginningIndexes[matchesSimple[0]-1]
+
+ // Our target string successfully matched all characters in sequence!
+ // Let's try a more advanced and strict test to improve the score
+ // only count it as a match if it's consecutive or a beginning character!
+ if(targetI !== targetLen) for(;;) {
+ if(targetI >= targetLen) {
+ // We failed to find a good spot for this search char, go back to the previous search char and force it forward
+ if(searchI <= 0) break // We failed to push chars forward for a better match
+
+ --searchI
+ var lastMatch = matchesStrict[--matchesStrictLen]
+ targetI = nextBeginningIndexes[lastMatch]
+
+ } else {
+ var isMatch = searchLowerCodes[searchI] === targetLowerCodes[targetI]
+ if(isMatch) {
+ matchesStrict[matchesStrictLen++] = targetI
+ ++searchI; if(searchI === searchLen) { successStrict = true; break }
+ ++targetI
+ } else {
+ targetI = nextBeginningIndexes[targetI]
+ }
+ }
+ }
+
+ { // tally up the score & keep track of matches for highlighting later
+ if(successStrict) { var matchesBest = matchesStrict; var matchesBestLen = matchesStrictLen }
+ else { var matchesBest = matchesSimple; var matchesBestLen = matchesSimpleLen }
+ var score = 0
+ var lastTargetI = -1
+ for(var i = 0; i < searchLen; ++i) { var targetI = matchesBest[i]
+ // score only goes down if they're not consecutive
+ if(lastTargetI !== targetI - 1) score -= targetI
+ lastTargetI = targetI
+ }
+ if(!successStrict) score *= 1000
+ score -= targetLen - searchLen
+ prepared.score = score
+ prepared.indexes = new Array(matchesBestLen); for(var i = matchesBestLen - 1; i >= 0; --i) prepared.indexes[i] = matchesBest[i]
+
+ return prepared
+ }
+ },
+
+ prepareLowerCodes: function(str) {
+ var strLen = str.length
+ var lowerCodes = [] // new Array(strLen) sparse array is too slow
+ var lower = str.toLowerCase()
+ for(var i = 0; i < strLen; ++i) lowerCodes[i] = lower.charCodeAt(i)
+ return lowerCodes
+ },
+ prepareBeginningIndexes: function(target) {
+ var targetLen = target.length
+ var beginningIndexes = []; var beginningIndexesLen = 0
+ var wasUpper = false
+ var wasAlphanum = false
+ for(var i = 0; i < targetLen; ++i) {
+ var targetCode = target.charCodeAt(i)
+ var isUpper = targetCode>=65&&targetCode<=90
+ var isAlphanum = isUpper || targetCode>=97&&targetCode<=122 || targetCode>=48&&targetCode<=57
+ var isBeginning = isUpper && !wasUpper || !wasAlphanum || !isAlphanum
+ wasUpper = isUpper
+ wasAlphanum = isAlphanum
+ if(isBeginning) beginningIndexes[beginningIndexesLen++] = i
+ }
+ return beginningIndexes
+ },
+ prepareNextBeginningIndexes: function(target) {
+ var targetLen = target.length
+ var beginningIndexes = fuzzysort.prepareBeginningIndexes(target)
+ var nextBeginningIndexes = [] // new Array(targetLen) sparse array is too slow
+ var lastIsBeginning = beginningIndexes[0]
+ var lastIsBeginningI = 0
+ for(var i = 0; i < targetLen; ++i) {
+ if(lastIsBeginning > i) {
+ nextBeginningIndexes[i] = lastIsBeginning
+ } else {
+ lastIsBeginning = beginningIndexes[++lastIsBeginningI]
+ nextBeginningIndexes[i] = lastIsBeginning===undefined ? targetLen : lastIsBeginning
+ }
+ }
+ return nextBeginningIndexes
+ },
+
+ cleanup: cleanup,
+ new: fuzzysortNew,
+ }
+ return fuzzysort
+} // fuzzysortNew
+
+// This stuff is outside fuzzysortNew, because it's shared with instances of fuzzysort.new()
+var isNode = typeof require !== 'undefined' && typeof window === 'undefined'
+// var MAX_INT = Number.MAX_SAFE_INTEGER
+// var MIN_INT = Number.MIN_VALUE
+var preparedCache = new Map()
+var preparedSearchCache = new Map()
+var noResults = []; noResults.total = 0
+var matchesSimple = []; var matchesStrict = []
+function cleanup() { preparedCache.clear(); preparedSearchCache.clear(); matchesSimple = []; matchesStrict = [] }
+function defaultScoreFn(a) {
+ var max = -9007199254740991
+ for (var i = a.length - 1; i >= 0; --i) {
+ var result = a[i]; if(result === null) continue
+ var score = result.score
+ if(score > max) max = score
+ }
+ if(max === -9007199254740991) return null
+ return max
+}
+
+// prop = 'key' 2.5ms optimized for this case, seems to be about as fast as direct obj[prop]
+// prop = 'key1.key2' 10ms
+// prop = ['key1', 'key2'] 27ms
+function getValue(obj, prop) {
+ var tmp = obj[prop]; if(tmp !== undefined) return tmp
+ var segs = prop
+ if(!Array.isArray(prop)) segs = prop.split('.')
+ var len = segs.length
+ var i = -1
+ while (obj && (++i < len)) obj = obj[segs[i]]
+ return obj
+}
+
+function isObj(x) { return typeof x === 'object' } // faster as a function
+
+// Hacked version of https://github.com/lemire/FastPriorityQueue.js
+var fastpriorityqueue=function(){var r=[],o=0,e={};function n(){for(var e=0,n=r[e],c=1;c>1]=r[e],c=1+(e<<1)}for(var a=e-1>>1;e>0&&n.score>1)r[e]=r[a];r[e]=n}return e.add=function(e){var n=o;r[o++]=e;for(var c=n-1>>1;n>0&&e.score>1)r[n]=r[c];r[n]=e},e.poll=function(){if(0!==o){var e=r[0];return r[0]=r[--o],n(),e}},e.peek=function(e){if(0!==o)return r[0]},e.replaceTop=function(o){r[0]=o,n()},e};
+var q = fastpriorityqueue() // reuse this, except for async, it needs to make its own
+
+return fuzzysortNew()
+}) // UMD
+
+// TODO: (performance) wasm version!?
+
+// TODO: (performance) layout memory in an optimal way to go fast by avoiding cache misses
+
+// TODO: (performance) preparedCache is a memory leak
+
+// TODO: (like sublime) backslash === forwardslash
+
+// TODO: (performance) i have no idea how well optizmied the allowing typos algorithm is
+
+'use strict';
+/**
+ * @requires ../node_modules/fuzzysort/fuzzysort.js
+ */
+
+function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
+
+function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); }
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
+
+function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }
+
+function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }
+
+var GhostSearch =
+/*#__PURE__*/
+function () {
+ function GhostSearch(args) {
+ _classCallCheck(this, GhostSearch);
+
+ this.check = false;
+ var defaults = {
+ input: '#ghost-search-field',
+ results: '#ghost-search-results',
+ button: '',
+ development: false,
+ template: function template(result) {
+ var url = [location.protocol, '//', location.host].join('');
+ return '' + result.title + '';
+ },
+ trigger: 'focus',
+ options: {
+ keys: ['title'],
+ limit: 10,
+ threshold: -3500,
+ allowTypo: false
+ },
+ api: {
+ resource: 'posts',
+ parameters: {
+ limit: 'all',
+ fields: ['title', 'slug'],
+ filter: '',
+ include: '',
+ order: '',
+ formats: ''
+ }
+ },
+ on: {
+ beforeDisplay: function beforeDisplay() {},
+ afterDisplay: function afterDisplay(results) {},
+ beforeFetch: function beforeFetch() {},
+ afterFetch: function afterFetch(results) {}
+ }
+ };
+ var merged = this.mergeDeep(defaults, args);
+ Object.assign(this, merged);
+ this.init();
+ }
+
+ _createClass(GhostSearch, [{
+ key: "mergeDeep",
+ value: function mergeDeep(target, source) {
+ var _this = this;
+
+ if (target && _typeof(target) === 'object' && !Array.isArray(target) && target !== null && source && _typeof(source) === 'object' && !Array.isArray(source) && source !== null) {
+ Object.keys(source).forEach(function (key) {
+ if (source[key] && _typeof(source[key]) === 'object' && !Array.isArray(source[key]) && source[key] !== null) {
+ if (!target[key]) Object.assign(target, _defineProperty({}, key, {}));
+
+ _this.mergeDeep(target[key], source[key]);
+ } else {
+ Object.assign(target, _defineProperty({}, key, source[key]));
+ }
+ });
+ }
+
+ return target;
+ }
+ }, {
+ key: "url",
+ value: function url() {
+ if (this.api.resource == 'posts' && this.api.parameters.include.match(/(tags|authors)/)) {
+ delete this.api.parameters.fields;
+ }
+
+ ;
+ var url = ghost.url.api(this.api.resource, this.api.parameters);
+ return url;
+ }
+ }, {
+ key: "fetch",
+ value: function (_fetch) {
+ function fetch() {
+ return _fetch.apply(this, arguments);
+ }
+
+ fetch.toString = function () {
+ return _fetch.toString();
+ };
+
+ return fetch;
+ }(function () {
+ var _this2 = this;
+
+ var url = this.url();
+ this.on.beforeFetch();
+ fetch(url).then(function (response) {
+ return response.json();
+ }).then(function (resource) {
+ return _this2.search(resource);
+ }).catch(function (error) {
+ return console.error("Fetch Error =\n", error);
+ });
+ })
+ }, {
+ key: "createElementFromHTML",
+ value: function createElementFromHTML(htmlString) {
+ var div = document.createElement('div');
+ div.innerHTML = htmlString.trim();
+ return div.firstChild;
+ }
+ }, {
+ key: "cleanup",
+ value: function cleanup(input) {
+ return input.toLowerCase().replace(/[^a-zA-Z0-9]+/g, "-");
+ }
+ }, {
+ key: "displayResults",
+ value: function displayResults(data) {
+ if (document.querySelectorAll(this.results)[0].firstChild !== null && document.querySelectorAll(this.results)[0].firstChild !== '') {
+ while (document.querySelectorAll(this.results)[0].firstChild) {
+ document.querySelectorAll(this.results)[0].removeChild(document.querySelectorAll(this.results)[0].firstChild);
+ }
+ }
+
+ ;
+ var inputValue = document.querySelectorAll(this.input)[0].value;
+ var results = fuzzysort.go(inputValue, data, {
+ keys: this.options.keys,
+ limit: this.options.limit,
+ allowTypo: this.options.allowTypo,
+ threshold: this.options.threshold
+ });
+
+ for (var key in results) {
+ if (key < results.length) {
+ document.querySelectorAll(this.results)[0].appendChild(this.createElementFromHTML(this.template(results[key].obj)));
+ }
+
+ ;
+ }
+
+ this.on.afterDisplay(results);
+ }
+ }, {
+ key: "search",
+ value: function search(resource) {
+ var _this3 = this;
+
+ var data = resource[this.api.resource];
+ this.on.afterFetch(data);
+ this.check = true;
+
+ if (this.button != '') {
+ var button = document.querySelectorAll(this.button)[0];
+
+ if (button.tagName == 'INPUT' && button.type == 'submit') {
+ button.closest('form').addEventListener("submit", function (e) {
+ e.preventDefault();
+ });
+ }
+
+ ;
+ button.addEventListener('click', function (e) {
+ e.preventDefault();
+
+ _this3.on.beforeDisplay();
+
+ _this3.displayResults(data);
+ });
+ } else {
+ document.querySelectorAll(this.input)[0].addEventListener('keyup', function (e) {
+ _this3.on.beforeDisplay();
+
+ _this3.displayResults(data);
+ });
+ }
+
+ ;
+ }
+ }, {
+ key: "checkGhostAPI",
+ value: function checkGhostAPI() {
+ if (typeof ghost === 'undefined') {
+ console.log('Ghost API is not enabled');
+ return false;
+ }
+
+ ;
+ return true;
+ }
+ }, {
+ key: "checkElements",
+ value: function checkElements() {
+ if (!document.querySelectorAll(this.input).length) {
+ console.log('Input not found.');
+ return false;
+ }
+
+ if (!document.querySelectorAll(this.results).length) {
+ console.log('Results not found.');
+ return false;
+ }
+
+ ;
+
+ if (this.button != '') {
+ if (!document.querySelectorAll(this.button).length) {
+ console.log('Button not found.');
+ return false;
+ }
+
+ ;
+ }
+
+ return true;
+ }
+ }, {
+ key: "checkFields",
+ value: function checkFields() {
+ var validFields = [];
+
+ if (this.api.resource == 'posts') {
+ validFields = ['amp', 'authors', 'codeinjection_foot', 'codeinjection_head', 'comment_id', 'created_at', 'created_by', 'custom_excerpt', 'custom_template', 'feature_image', 'featured', 'html', 'id', 'locale', 'meta_description', 'meta_title', 'mobiledoc', 'og_description', 'og_image', 'og_title', 'page', 'plaintext', 'primary_author', 'primary_tag', 'published_at', 'published_by', 'slug', 'status', 'tags', 'title', 'twitter_description', 'twitter_image', 'twitter_title', 'updated_at', 'updated_by', 'url', 'uuid', 'visibility'];
+ } else if (this.api.resource == 'tags') {
+ validFields = ['count', 'created_at', 'created_by', 'description', 'feature_image', 'id', 'meta_description', 'meta_title', 'name', 'parent', 'slug', 'updated_at', 'updated_by', 'visibility'];
+ } else if (this.api.resource == 'users') {
+ validFields = ['accessibility', 'bio', 'count', 'cover_image', 'facebook', 'id', 'locale', 'location', 'meta_description', 'meta_title', 'name', 'profile_image', 'slug', 'tour', 'twitter', 'visibility', 'website'];
+ }
+
+ for (var i = 0; i < this.api.parameters.fields.length; i++) {
+ if (!validFields.includes(this.api.parameters.fields[i])) {
+ console.log('\'' + this.api.parameters.fields[i] + '\' is not a valid field for ' + this.api.resource + '. Valid fields for ' + this.api.resource + ': [\'' + validFields.join('\', \'') + '\']');
+ }
+ }
+ }
+ }, {
+ key: "checkFormats",
+ value: function checkFormats() {
+ if (this.api.resource == 'posts' && this.api.parameters.fields && _typeof(this.api.parameters.fields) === 'object' && this.api.parameters.fields.constructor === Array) {
+ for (var i = 0; i < this.api.parameters.fields.length; i++) {
+ if (!this.api.parameters.formats.includes(this.api.parameters.fields[i]) && this.api.parameters.fields[i].match(/(plaintext|mobiledoc|amp)/) || this.api.parameters.fields[i] == 'html' && this.api.parameters.formats.length > 0 && !this.api.parameters.formats.includes('html')) {
+ console.log(this.api.parameters.fields[i] + ' is not included in the formats parameter.');
+ }
+ }
+ }
+ }
+ }, {
+ key: "checkKeys",
+ value: function checkKeys() {
+ var _this4 = this;
+
+ if (!this.options.keys.every(function (elem) {
+ return _this4.api.parameters.fields.indexOf(elem) > -1;
+ })) {
+ console.log('Not all keys are in fields. Please add them.');
+ }
+
+ ;
+ }
+ }, {
+ key: "validate",
+ value: function validate() {
+ if (!this.checkGhostAPI() || !this.checkElements()) {
+ return false;
+ }
+
+ ;
+
+ if (this.development) {
+ this.checkFields();
+ this.checkFormats();
+ this.checkKeys();
+ }
+
+ ;
+ return true;
+ }
+ }, {
+ key: "init",
+ value: function init() {
+ var _this5 = this;
+
+ if (!this.validate()) {
+ return;
+ }
+
+ if (this.trigger == 'focus') {
+ document.querySelectorAll(this.input)[0].addEventListener('focus', function (e) {
+ if (!_this5.check) {
+ _this5.fetch();
+ }
+
+ ;
+ });
+ } else if (this.trigger == 'load') {
+ window.onload = function () {
+ if (!_this5.check) {
+ _this5.fetch();
+ }
+
+ ;
+ };
+ }
+ }
+ }]);
+
+ return GhostSearch;
+}();
+//# sourceMappingURL=ghost-search.js.map
diff --git a/dist/ghost-search.js.map b/dist/ghost-search.js.map
new file mode 100644
index 0000000..6d5715d
--- /dev/null
+++ b/dist/ghost-search.js.map
@@ -0,0 +1 @@
+{"version":3,"names":[],"mappings":"","sources":["ghost-search.js"],"sourcesContent":["'use strict';\n/**\r\n * @requires ../node_modules/fuzzysort/fuzzysort.js\r\n */\n\nfunction _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }\n\nfunction _typeof(obj) { if (typeof Symbol === \"function\" && typeof Symbol.iterator === \"symbol\") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === \"function\" && obj.constructor === Symbol && obj !== Symbol.prototype ? \"symbol\" : typeof obj; }; } return _typeof(obj); }\n\nfunction _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(\"Cannot call a class as a function\"); } }\n\nfunction _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if (\"value\" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }\n\nfunction _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }\n\nvar GhostSearch =\n/*#__PURE__*/\nfunction () {\n function GhostSearch(args) {\n _classCallCheck(this, GhostSearch);\n\n this.check = false;\n var defaults = {\n input: '#ghost-search-field',\n results: '#ghost-search-results',\n button: '',\n development: false,\n template: function template(result) {\n var url = [location.protocol, '//', location.host].join('');\n return '' + result.title + '';\n },\n trigger: 'focus',\n options: {\n keys: ['title'],\n limit: 10,\n threshold: -3500,\n allowTypo: false\n },\n api: {\n resource: 'posts',\n parameters: {\n limit: 'all',\n fields: ['title', 'slug'],\n filter: '',\n include: '',\n order: '',\n formats: ''\n }\n },\n on: {\n beforeDisplay: function beforeDisplay() {},\n afterDisplay: function afterDisplay(results) {},\n beforeFetch: function beforeFetch() {},\n afterFetch: function afterFetch(results) {}\n }\n };\n var merged = this.mergeDeep(defaults, args);\n Object.assign(this, merged);\n this.init();\n }\n\n _createClass(GhostSearch, [{\n key: \"mergeDeep\",\n value: function mergeDeep(target, source) {\n var _this = this;\n\n if (target && _typeof(target) === 'object' && !Array.isArray(target) && target !== null && source && _typeof(source) === 'object' && !Array.isArray(source) && source !== null) {\n Object.keys(source).forEach(function (key) {\n if (source[key] && _typeof(source[key]) === 'object' && !Array.isArray(source[key]) && source[key] !== null) {\n if (!target[key]) Object.assign(target, _defineProperty({}, key, {}));\n\n _this.mergeDeep(target[key], source[key]);\n } else {\n Object.assign(target, _defineProperty({}, key, source[key]));\n }\n });\n }\n\n return target;\n }\n }, {\n key: \"url\",\n value: function url() {\n if (this.api.resource == 'posts' && this.api.parameters.include.match(/(tags|authors)/)) {\n delete this.api.parameters.fields;\n }\n\n ;\n var url = ghost.url.api(this.api.resource, this.api.parameters);\n return url;\n }\n }, {\n key: \"fetch\",\n value: function (_fetch) {\n function fetch() {\n return _fetch.apply(this, arguments);\n }\n\n fetch.toString = function () {\n return _fetch.toString();\n };\n\n return fetch;\n }(function () {\n var _this2 = this;\n\n var url = this.url();\n this.on.beforeFetch();\n fetch(url).then(function (response) {\n return response.json();\n }).then(function (resource) {\n return _this2.search(resource);\n }).catch(function (error) {\n return console.error(\"Fetch Error =\\n\", error);\n });\n })\n }, {\n key: \"createElementFromHTML\",\n value: function createElementFromHTML(htmlString) {\n var div = document.createElement('div');\n div.innerHTML = htmlString.trim();\n return div.firstChild;\n }\n }, {\n key: \"cleanup\",\n value: function cleanup(input) {\n return input.toLowerCase().replace(/[^a-zA-Z0-9]+/g, \"-\");\n }\n }, {\n key: \"displayResults\",\n value: function displayResults(data) {\n if (document.querySelectorAll(this.results)[0].firstChild !== null && document.querySelectorAll(this.results)[0].firstChild !== '') {\n while (document.querySelectorAll(this.results)[0].firstChild) {\n document.querySelectorAll(this.results)[0].removeChild(document.querySelectorAll(this.results)[0].firstChild);\n }\n }\n\n ;\n var inputValue = document.querySelectorAll(this.input)[0].value;\n var results = fuzzysort.go(inputValue, data, {\n keys: this.options.keys,\n limit: this.options.limit,\n allowTypo: this.options.allowTypo,\n threshold: this.options.threshold\n });\n\n for (var key in results) {\n if (key < results.length) {\n document.querySelectorAll(this.results)[0].appendChild(this.createElementFromHTML(this.template(results[key].obj)));\n }\n\n ;\n }\n\n this.on.afterDisplay(results);\n }\n }, {\n key: \"search\",\n value: function search(resource) {\n var _this3 = this;\n\n var data = resource[this.api.resource];\n this.on.afterFetch(data);\n this.check = true;\n\n if (this.button != '') {\n var button = document.querySelectorAll(this.button)[0];\n\n if (button.tagName == 'INPUT' && button.type == 'submit') {\n button.closest('form').addEventListener(\"submit\", function (e) {\n e.preventDefault();\n });\n }\n\n ;\n button.addEventListener('click', function (e) {\n e.preventDefault();\n\n _this3.on.beforeDisplay();\n\n _this3.displayResults(data);\n });\n } else {\n document.querySelectorAll(this.input)[0].addEventListener('keyup', function (e) {\n _this3.on.beforeDisplay();\n\n _this3.displayResults(data);\n });\n }\n\n ;\n }\n }, {\n key: \"checkGhostAPI\",\n value: function checkGhostAPI() {\n if (typeof ghost === 'undefined') {\n console.log('Ghost API is not enabled');\n return false;\n }\n\n ;\n return true;\n }\n }, {\n key: \"checkElements\",\n value: function checkElements() {\n if (!document.querySelectorAll(this.input).length) {\n console.log('Input not found.');\n return false;\n }\n\n if (!document.querySelectorAll(this.results).length) {\n console.log('Results not found.');\n return false;\n }\n\n ;\n\n if (this.button != '') {\n if (!document.querySelectorAll(this.button).length) {\n console.log('Button not found.');\n return false;\n }\n\n ;\n }\n\n return true;\n }\n }, {\n key: \"checkFields\",\n value: function checkFields() {\n var validFields = [];\n\n if (this.api.resource == 'posts') {\n validFields = ['amp', 'authors', 'codeinjection_foot', 'codeinjection_head', 'comment_id', 'created_at', 'created_by', 'custom_excerpt', 'custom_template', 'feature_image', 'featured', 'html', 'id', 'locale', 'meta_description', 'meta_title', 'mobiledoc', 'og_description', 'og_image', 'og_title', 'page', 'plaintext', 'primary_author', 'primary_tag', 'published_at', 'published_by', 'slug', 'status', 'tags', 'title', 'twitter_description', 'twitter_image', 'twitter_title', 'updated_at', 'updated_by', 'url', 'uuid', 'visibility'];\n } else if (this.api.resource == 'tags') {\n validFields = ['count', 'created_at', 'created_by', 'description', 'feature_image', 'id', 'meta_description', 'meta_title', 'name', 'parent', 'slug', 'updated_at', 'updated_by', 'visibility'];\n } else if (this.api.resource == 'users') {\n validFields = ['accessibility', 'bio', 'count', 'cover_image', 'facebook', 'id', 'locale', 'location', 'meta_description', 'meta_title', 'name', 'profile_image', 'slug', 'tour', 'twitter', 'visibility', 'website'];\n }\n\n for (var i = 0; i < this.api.parameters.fields.length; i++) {\n if (!validFields.includes(this.api.parameters.fields[i])) {\n console.log('\\'' + this.api.parameters.fields[i] + '\\' is not a valid field for ' + this.api.resource + '. Valid fields for ' + this.api.resource + ': [\\'' + validFields.join('\\', \\'') + '\\']');\n }\n }\n }\n }, {\n key: \"checkFormats\",\n value: function checkFormats() {\n if (this.api.resource == 'posts' && this.api.parameters.fields && _typeof(this.api.parameters.fields) === 'object' && this.api.parameters.fields.constructor === Array) {\n for (var i = 0; i < this.api.parameters.fields.length; i++) {\n if (!this.api.parameters.formats.includes(this.api.parameters.fields[i]) && this.api.parameters.fields[i].match(/(plaintext|mobiledoc|amp)/) || this.api.parameters.fields[i] == 'html' && this.api.parameters.formats.length > 0 && !this.api.parameters.formats.includes('html')) {\n console.log(this.api.parameters.fields[i] + ' is not included in the formats parameter.');\n }\n }\n }\n }\n }, {\n key: \"checkKeys\",\n value: function checkKeys() {\n var _this4 = this;\n\n if (!this.options.keys.every(function (elem) {\n return _this4.api.parameters.fields.indexOf(elem) > -1;\n })) {\n console.log('Not all keys are in fields. Please add them.');\n }\n\n ;\n }\n }, {\n key: \"validate\",\n value: function validate() {\n if (!this.checkGhostAPI() || !this.checkElements()) {\n return false;\n }\n\n ;\n\n if (this.development) {\n this.checkFields();\n this.checkFormats();\n this.checkKeys();\n }\n\n ;\n return true;\n }\n }, {\n key: \"init\",\n value: function init() {\n var _this5 = this;\n\n if (!this.validate()) {\n return;\n }\n\n if (this.trigger == 'focus') {\n document.querySelectorAll(this.input)[0].addEventListener('focus', function (e) {\n if (!_this5.check) {\n _this5.fetch();\n }\n\n ;\n });\n } else if (this.trigger == 'load') {\n window.onload = function () {\n if (!_this5.check) {\n _this5.fetch();\n }\n\n ;\n };\n }\n }\n }]);\n\n return GhostSearch;\n}();"],"file":"ghost-search.js"}
\ No newline at end of file
diff --git a/dist/ghost-search.min.js b/dist/ghost-search.min.js
new file mode 100644
index 0000000..ac26b27
--- /dev/null
+++ b/dist/ghost-search.min.js
@@ -0,0 +1,10 @@
+/**
+ * ghost-search 0.1.0 (https://github.com/HauntedThemes/ghost-search)
+ * A simple but powerful search library for Ghost Blogging Platform.
+ * Copyright 2018 Haunted Themes (https://www.hauntedthemes.com)
+ * Released under MIT License
+ * Released on: 17 Sep 2018
+ */
+
+function _defineProperty(e,t,r){return t in e?Object.defineProperty(e,t,{value:r,enumerable:!0,configurable:!0,writable:!0}):e[t]=r,e}function _typeof(e){return(_typeof="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}function _classCallCheck(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function _defineProperties(e,t){for(var r=0;r=0;--r){var n=e[r];if(null!==n){var o=n.score;o>t&&(t=o)}}return-9007199254740991===t?null:t}function s(e,t){var r=e[t];if(void 0!==r)return r;var n=t;Array.isArray(t)||(n=t.split("."));for(var o=n.length,i=-1;e&&++i>1]=e[r],o=1+(r<<1)}for(var a=r-1>>1;r>0&&n.score>1)e[r]=e[a];e[r]=n}return r.add=function(r){var n=t;e[t++]=r;for(var o=n-1>>1;n>0&&r.score>1)e[n]=e[o];e[n]=r},r.poll=function(){if(0!==t){var r=e[0];return e[0]=e[--t],n(),r}},r.peek=function(r){if(0!==t)return e[0]},r.replaceTop=function(t){e[0]=t,n()},r},f=c();return function p(d){var h={single:function(e,t,r){return e?(u(e)||(e=h.getPreparedSearch(e)),t?(u(t)||(t=h.getPrepared(t)),((r&&void 0!==r.allowTypo?r.allowTypo:!d||void 0===d.allowTypo||d.allowTypo)?h.algorithm:h.algorithmNoTypo)(e,t,e[0])):null):null},go:function(e,t,r){if(!e)return n;var o=(e=h.prepareSearch(e))[0],i=r&&r.threshold||d&&d.threshold||-9007199254740991,a=r&&r.limit||d&&d.limit||9007199254740991,c=(r&&void 0!==r.allowTypo?r.allowTypo:!d||void 0===d.allowTypo||d.allowTypo)?h.algorithm:h.algorithmNoTypo,p=0,g=0,y=t.length;if(r&&r.keys)for(var m=r.scoreFn||l,v=r.keys,b=v.length,k=y-1;k>=0;--k){for(var _=t[k],w=new Array(b),x=b-1;x>=0;--x)(S=s(_,T=v[x]))?(u(S)||(S=h.getPrepared(S)),w[x]=c(e,S,o)):w[x]=null;w.obj=_;var A=m(w);null!==A&&(Af.peek().score&&f.replaceTop(w))))}else if(r&&r.key){var T=r.key;for(k=y-1;k>=0;--k)(S=s(_=t[k],T))&&(u(S)||(S=h.getPrepared(S)),null!==(C=c(e,S,o))&&(C.scoref.peek().score&&f.replaceTop(C)))))}else for(k=y-1;k>=0;--k){var S,C;(S=t[k])&&(u(S)||(S=h.getPrepared(S)),null!==(C=c(e,S,o))&&(C.scoref.peek().score&&f.replaceTop(C)))))}if(0===p)return n;var j=new Array(p);for(k=p-1;k>=0;--k)j[k]=f.poll();return j.total=p+g,j},goAsync:function(t,r,o){var i=!1,a=new Promise(function(a,f){if(!t)return a(n);var p=(t=h.prepareSearch(t))[0],g=c(),y=r.length-1,m=o&&o.threshold||d&&d.threshold||-9007199254740991,v=o&&o.limit||d&&d.limit||9007199254740991,b=(o&&void 0!==o.allowTypo?o.allowTypo:!d||void 0===d.allowTypo||d.allowTypo)?h.algorithm:h.algorithmNoTypo,k=0,_=0;function w(){if(i)return f("canceled");var c=Date.now();if(o&&o.keys)for(var d=o.scoreFn||l,x=o.keys,A=x.length;y>=0;--y){for(var T=r[y],S=new Array(A),C=A-1;C>=0;--C)(I=s(T,P=x[C]))?(u(I)||(I=h.getPrepared(I)),S[C]=b(t,I,p)):S[C]=null;S.obj=T;var j=d(S);if(null!==j&&!(jg.peek().score&&g.replaceTop(S)),y%1e3==0&&Date.now()-c>=10))return void(e?setImmediate(w):setTimeout(w))}else if(o&&o.key){for(var P=o.key;y>=0;--y)if((I=s(T=r[y],P))&&(u(I)||(I=h.getPrepared(I)),null!==(L=b(t,I,p))&&!(L.scoreg.peek().score&&g.replaceTop(L)),y%1e3==0&&Date.now()-c>=10)))return void(e?setImmediate(w):setTimeout(w))}else for(;y>=0;--y){var I,L;if((I=r[y])&&(u(I)||(I=h.getPrepared(I)),null!==(L=b(t,I,p))&&!(L.scoreg.peek().score&&g.replaceTop(L)),y%1e3==0&&Date.now()-c>=10)))return void(e?setImmediate(w):setTimeout(w))}if(0===k)return a(n);for(var B=new Array(k),q=k-1;q>=0;--q)B[q]=g.poll();B.total=k+_,a(B)}e?setImmediate(w):w()});return a.cancel=function(){i=!0},a},highlight:function(e,t,r){if(null===e)return null;void 0===t&&(t=""),void 0===r&&(r="");for(var n="",o=0,i=!1,a=e.target,l=a.length,s=e.indexes,u=0;u999)return h.prepare(e);var r=t.get(e);return void 0!==r?r:(r=h.prepare(e),t.set(e,r),r)},getPreparedSearch:function(e){if(e.length>999)return h.prepareSearch(e);var t=r.get(e);return void 0!==t?t:(t=h.prepareSearch(e),r.set(e,t),t)},algorithm:function(e,t,r){for(var n=t._targetLowerCodes,a=e.length,l=n.length,s=0,u=0,c=0,f=0;;){if(r===n[u]){if(o[f++]=u,++s===a)break;r=e[0===c?s:c===s?s+1:c===s-1?s-1:s]}if(++u>=l)for(;;){if(s<=1)return null;if(0===c){if(r===e[--s])continue;c=s}else{if(1===c)return null;if((r=e[1+(s=--c)])===e[s])continue}u=o[(f=s)-1]+1;break}}s=0;var p=0,d=!1,g=0,y=t._nextBeginningIndexes;null===y&&(y=t._nextBeginningIndexes=h.prepareNextBeginningIndexes(t.target));var m=u=0===o[0]?0:y[o[0]-1];if(u!==l)for(;;)if(u>=l){if(s<=0){if(++p>a-2)break;if(e[p]===e[p+1])continue;u=m;continue}--s,u=y[i[--g]]}else if(e[0===p?s:p===s?s+1:p===s-1?s-1:s]===n[u]){if(i[g++]=u,++s===a){d=!0;break}++u}else u=y[u];if(d)var v=i,b=g;else v=o,b=f;for(var k=0,_=-1,w=0;w=0;--w)t.indexes[w]=v[w];return t},algorithmNoTypo:function(e,t,r){for(var n=t._targetLowerCodes,a=e.length,l=n.length,s=0,u=0,c=0;;){if(r===n[u]){if(o[c++]=u,++s===a)break;r=e[s]}if(++u>=l)return null}s=0;var f=!1,p=0,d=t._nextBeginningIndexes;if(null===d&&(d=t._nextBeginningIndexes=h.prepareNextBeginningIndexes(t.target)),(u=0===o[0]?0:d[o[0]-1])!==l)for(;;)if(u>=l){if(s<=0)break;--s,u=d[i[--p]]}else if(e[s]===n[u]){if(i[p++]=u,++s===a){f=!0;break}++u}else u=d[u];if(f)var g=i,y=p;else g=o,y=c;for(var m=0,v=-1,b=0;b=0;--b)t.indexes[b]=g[b];return t},prepareLowerCodes:function(e){for(var t=e.length,r=[],n=e.toLowerCase(),o=0;o=65&&l<=90,u=s||l>=97&&l<=122||l>=48&&l<=57,c=s&&!o||!i||!u;o=s,i=u,c&&(r[n++]=a)}return r},prepareNextBeginningIndexes:function(e){for(var t=e.length,r=h.prepareBeginningIndexes(e),n=[],o=r[0],i=0,a=0;aa?n[a]=o:(o=r[++i],n[a]=void 0===o?t:o);return n},cleanup:a,new:p};return h}()});var GhostSearch=function(){function e(t){_classCallCheck(this,e),this.check=!1;var r={input:"#ghost-search-field",results:"#ghost-search-results",button:"",development:!1,template:function(e){return''+e.title+""},trigger:"focus",options:{keys:["title"],limit:10,threshold:-3500,allowTypo:!1},api:{resource:"posts",parameters:{limit:"all",fields:["title","slug"],filter:"",include:"",order:"",formats:""}},on:{beforeDisplay:function(){},afterDisplay:function(e){},beforeFetch:function(){},afterFetch:function(e){}}},n=this.mergeDeep(r,t);Object.assign(this,n),this.init()}return _createClass(e,[{key:"mergeDeep",value:function(e,t){var r=this;return e&&"object"===_typeof(e)&&!Array.isArray(e)&&null!==e&&t&&"object"===_typeof(t)&&!Array.isArray(t)&&null!==t&&Object.keys(t).forEach(function(n){t[n]&&"object"===_typeof(t[n])&&!Array.isArray(t[n])&&null!==t[n]?(e[n]||Object.assign(e,_defineProperty({},n,{})),r.mergeDeep(e[n],t[n])):Object.assign(e,_defineProperty({},n,t[n]))}),e}},{key:"url",value:function(){"posts"==this.api.resource&&this.api.parameters.include.match(/(tags|authors)/)&&delete this.api.parameters.fields;var e=ghost.url.api(this.api.resource,this.api.parameters);return e}},{key:"fetch",value:function(e){function t(){return e.apply(this,arguments)}return t.toString=function(){return e.toString()},t}(function(){var e=this,t=this.url();this.on.beforeFetch(),fetch(t).then(function(e){return e.json()}).then(function(t){return e.search(t)}).catch(function(e){return console.error("Fetch Error =\n",e)})})},{key:"createElementFromHTML",value:function(e){var t=document.createElement("div");return t.innerHTML=e.trim(),t.firstChild}},{key:"cleanup",value:function(e){return e.toLowerCase().replace(/[^a-zA-Z0-9]+/g,"-")}},{key:"displayResults",value:function(e){if(null!==document.querySelectorAll(this.results)[0].firstChild&&""!==document.querySelectorAll(this.results)[0].firstChild)for(;document.querySelectorAll(this.results)[0].firstChild;)document.querySelectorAll(this.results)[0].removeChild(document.querySelectorAll(this.results)[0].firstChild);var t=document.querySelectorAll(this.input)[0].value,r=fuzzysort.go(t,e,{keys:this.options.keys,limit:this.options.limit,allowTypo:this.options.allowTypo,threshold:this.options.threshold});for(var n in r)n0&&!this.api.parameters.formats.includes("html"))&&console.log(this.api.parameters.fields[e]+" is not included in the formats parameter.")}},{key:"checkKeys",value:function(){var e=this;this.options.keys.every(function(t){return e.api.parameters.fields.indexOf(t)>-1})||console.log("Not all keys are in fields. Please add them.")}},{key:"validate",value:function(){return!(!this.checkGhostAPI()||!this.checkElements())&&(this.development&&(this.checkFields(),this.checkFormats(),this.checkKeys()),!0)}},{key:"init",value:function(){var e=this;this.validate()&&("focus"==this.trigger?document.querySelectorAll(this.input)[0].addEventListener("focus",function(t){e.check||e.fetch()}):"load"==this.trigger&&(window.onload=function(){e.check||e.fetch()}))}}]),e}();
+//# sourceMappingURL=ghost-search.min.js.map
diff --git a/dist/ghost-search.min.js.map b/dist/ghost-search.min.js.map
new file mode 100644
index 0000000..da36069
--- /dev/null
+++ b/dist/ghost-search.min.js.map
@@ -0,0 +1 @@
+{"version":3,"sources":["ghost-search.js"],"names":["_defineProperty","obj","key","value","Object","defineProperty","enumerable","configurable","writable","_typeof","Symbol","iterator","constructor","prototype","_classCallCheck","instance","Constructor","TypeError","_defineProperties","target","props","i","length","descriptor","_createClass","protoProps","staticProps","root","UMD","define","amd","module","exports","fuzzysort","this","isNode","require","window","preparedCache","Map","preparedSearchCache","noResults","total","matchesSimple","matchesStrict","cleanup","clear","defaultScoreFn","a","max","result","score","getValue","prop","tmp","undefined","segs","Array","isArray","split","len","isObj","x","fastpriorityqueue","r","o","e","n","c","f","add","poll","peek","replaceTop","q","fuzzysortNew","instanceOptions","single","search","options","getPreparedSearch","getPrepared","allowTypo","algorithm","algorithmNoTypo","go","targets","searchLowerCode","prepareSearch","threshold","limit","resultsLen","limitedCount","targetsLen","keys","scoreFn","keysLen","objResults","keyI","_targetLowerCodes","_nextBeginningIndexes","indexes","results","goAsync","canceled","p","Promise","resolve","reject","iCurrent","step","startMs","Date","now","setImmediate","setTimeout","cancel","highlight","hOpen","hClose","highlighted","matchesIndex","opened","targetLen","matchesBest","char","substr","prepare","prepareLowerCodes","prepareSlow","prepareNextBeginningIndexes","targetPrepared","get","set","searchPrepared","searchLowerCodes","prepared","targetLowerCodes","searchLen","searchI","targetI","typoSimpleI","matchesSimpleLen","typoStrictI","successStrict","matchesStrictLen","nextBeginningIndexes","firstPossibleI","matchesBestLen","lastTargetI","str","strLen","lowerCodes","lower","toLowerCase","charCodeAt","prepareBeginningIndexes","beginningIndexes","beginningIndexesLen","wasUpper","wasAlphanum","targetCode","isUpper","isAlphanum","isBeginning","lastIsBeginning","lastIsBeginningI","new","GhostSearch","args","check","defaults","input","button","development","template","location","protocol","host","join","slug","title","trigger","api","resource","parameters","fields","filter","include","order","formats","on","beforeDisplay","afterDisplay","beforeFetch","afterFetch","merged","mergeDeep","assign","init","source","_this","forEach","match","url","ghost","_fetch","fetch","apply","arguments","toString","_this2","then","response","json","catch","error","console","htmlString","div","document","createElement","innerHTML","trim","firstChild","replace","data","querySelectorAll","removeChild","inputValue","appendChild","createElementFromHTML","_this3","tagName","type","closest","addEventListener","preventDefault","displayResults","log","validFields","includes","_this4","every","elem","indexOf","checkGhostAPI","checkElements","checkFields","checkFormats","checkKeys","_this5","validate","onload"],"mappings":"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","sourcesContent":["'use strict';\n/**\r\n * @requires ../node_modules/fuzzysort/fuzzysort.js\r\n */\n\nfunction _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }\n\nfunction _typeof(obj) { if (typeof Symbol === \"function\" && typeof Symbol.iterator === \"symbol\") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === \"function\" && obj.constructor === Symbol && obj !== Symbol.prototype ? \"symbol\" : typeof obj; }; } return _typeof(obj); }\n\nfunction _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(\"Cannot call a class as a function\"); } }\n\nfunction _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if (\"value\" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }\n\nfunction _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }\n\nvar GhostSearch =\n/*#__PURE__*/\nfunction () {\n function GhostSearch(args) {\n _classCallCheck(this, GhostSearch);\n\n this.check = false;\n var defaults = {\n input: '#ghost-search-field',\n results: '#ghost-search-results',\n button: '',\n development: false,\n template: function template(result) {\n var url = [location.protocol, '//', location.host].join('');\n return '' + result.title + '';\n },\n trigger: 'focus',\n options: {\n keys: ['title'],\n limit: 10,\n threshold: -3500,\n allowTypo: false\n },\n api: {\n resource: 'posts',\n parameters: {\n limit: 'all',\n fields: ['title', 'slug'],\n filter: '',\n include: '',\n order: '',\n formats: ''\n }\n },\n on: {\n beforeDisplay: function beforeDisplay() {},\n afterDisplay: function afterDisplay(results) {},\n beforeFetch: function beforeFetch() {},\n afterFetch: function afterFetch(results) {}\n }\n };\n var merged = this.mergeDeep(defaults, args);\n Object.assign(this, merged);\n this.init();\n }\n\n _createClass(GhostSearch, [{\n key: \"mergeDeep\",\n value: function mergeDeep(target, source) {\n var _this = this;\n\n if (target && _typeof(target) === 'object' && !Array.isArray(target) && target !== null && source && _typeof(source) === 'object' && !Array.isArray(source) && source !== null) {\n Object.keys(source).forEach(function (key) {\n if (source[key] && _typeof(source[key]) === 'object' && !Array.isArray(source[key]) && source[key] !== null) {\n if (!target[key]) Object.assign(target, _defineProperty({}, key, {}));\n\n _this.mergeDeep(target[key], source[key]);\n } else {\n Object.assign(target, _defineProperty({}, key, source[key]));\n }\n });\n }\n\n return target;\n }\n }, {\n key: \"url\",\n value: function url() {\n if (this.api.resource == 'posts' && this.api.parameters.include.match(/(tags|authors)/)) {\n delete this.api.parameters.fields;\n }\n\n ;\n var url = ghost.url.api(this.api.resource, this.api.parameters);\n return url;\n }\n }, {\n key: \"fetch\",\n value: function (_fetch) {\n function fetch() {\n return _fetch.apply(this, arguments);\n }\n\n fetch.toString = function () {\n return _fetch.toString();\n };\n\n return fetch;\n }(function () {\n var _this2 = this;\n\n var url = this.url();\n this.on.beforeFetch();\n fetch(url).then(function (response) {\n return response.json();\n }).then(function (resource) {\n return _this2.search(resource);\n }).catch(function (error) {\n return console.error(\"Fetch Error =\\n\", error);\n });\n })\n }, {\n key: \"createElementFromHTML\",\n value: function createElementFromHTML(htmlString) {\n var div = document.createElement('div');\n div.innerHTML = htmlString.trim();\n return div.firstChild;\n }\n }, {\n key: \"cleanup\",\n value: function cleanup(input) {\n return input.toLowerCase().replace(/[^a-zA-Z0-9]+/g, \"-\");\n }\n }, {\n key: \"displayResults\",\n value: function displayResults(data) {\n if (document.querySelectorAll(this.results)[0].firstChild !== null && document.querySelectorAll(this.results)[0].firstChild !== '') {\n while (document.querySelectorAll(this.results)[0].firstChild) {\n document.querySelectorAll(this.results)[0].removeChild(document.querySelectorAll(this.results)[0].firstChild);\n }\n }\n\n ;\n var inputValue = document.querySelectorAll(this.input)[0].value;\n var results = fuzzysort.go(inputValue, data, {\n keys: this.options.keys,\n limit: this.options.limit,\n allowTypo: this.options.allowTypo,\n threshold: this.options.threshold\n });\n\n for (var key in results) {\n if (key < results.length) {\n document.querySelectorAll(this.results)[0].appendChild(this.createElementFromHTML(this.template(results[key].obj)));\n }\n\n ;\n }\n\n this.on.afterDisplay(results);\n }\n }, {\n key: \"search\",\n value: function search(resource) {\n var _this3 = this;\n\n var data = resource[this.api.resource];\n this.on.afterFetch(data);\n this.check = true;\n\n if (this.button != '') {\n var button = document.querySelectorAll(this.button)[0];\n\n if (button.tagName == 'INPUT' && button.type == 'submit') {\n button.closest('form').addEventListener(\"submit\", function (e) {\n e.preventDefault();\n });\n }\n\n ;\n button.addEventListener('click', function (e) {\n e.preventDefault();\n\n _this3.on.beforeDisplay();\n\n _this3.displayResults(data);\n });\n } else {\n document.querySelectorAll(this.input)[0].addEventListener('keyup', function (e) {\n _this3.on.beforeDisplay();\n\n _this3.displayResults(data);\n });\n }\n\n ;\n }\n }, {\n key: \"checkGhostAPI\",\n value: function checkGhostAPI() {\n if (typeof ghost === 'undefined') {\n console.log('Ghost API is not enabled');\n return false;\n }\n\n ;\n return true;\n }\n }, {\n key: \"checkElements\",\n value: function checkElements() {\n if (!document.querySelectorAll(this.input).length) {\n console.log('Input not found.');\n return false;\n }\n\n if (!document.querySelectorAll(this.results).length) {\n console.log('Results not found.');\n return false;\n }\n\n ;\n\n if (this.button != '') {\n if (!document.querySelectorAll(this.button).length) {\n console.log('Button not found.');\n return false;\n }\n\n ;\n }\n\n return true;\n }\n }, {\n key: \"checkFields\",\n value: function checkFields() {\n var validFields = [];\n\n if (this.api.resource == 'posts') {\n validFields = ['amp', 'authors', 'codeinjection_foot', 'codeinjection_head', 'comment_id', 'created_at', 'created_by', 'custom_excerpt', 'custom_template', 'feature_image', 'featured', 'html', 'id', 'locale', 'meta_description', 'meta_title', 'mobiledoc', 'og_description', 'og_image', 'og_title', 'page', 'plaintext', 'primary_author', 'primary_tag', 'published_at', 'published_by', 'slug', 'status', 'tags', 'title', 'twitter_description', 'twitter_image', 'twitter_title', 'updated_at', 'updated_by', 'url', 'uuid', 'visibility'];\n } else if (this.api.resource == 'tags') {\n validFields = ['count', 'created_at', 'created_by', 'description', 'feature_image', 'id', 'meta_description', 'meta_title', 'name', 'parent', 'slug', 'updated_at', 'updated_by', 'visibility'];\n } else if (this.api.resource == 'users') {\n validFields = ['accessibility', 'bio', 'count', 'cover_image', 'facebook', 'id', 'locale', 'location', 'meta_description', 'meta_title', 'name', 'profile_image', 'slug', 'tour', 'twitter', 'visibility', 'website'];\n }\n\n for (var i = 0; i < this.api.parameters.fields.length; i++) {\n if (!validFields.includes(this.api.parameters.fields[i])) {\n console.log('\\'' + this.api.parameters.fields[i] + '\\' is not a valid field for ' + this.api.resource + '. Valid fields for ' + this.api.resource + ': [\\'' + validFields.join('\\', \\'') + '\\']');\n }\n }\n }\n }, {\n key: \"checkFormats\",\n value: function checkFormats() {\n if (this.api.resource == 'posts' && this.api.parameters.fields && _typeof(this.api.parameters.fields) === 'object' && this.api.parameters.fields.constructor === Array) {\n for (var i = 0; i < this.api.parameters.fields.length; i++) {\n if (!this.api.parameters.formats.includes(this.api.parameters.fields[i]) && this.api.parameters.fields[i].match(/(plaintext|mobiledoc|amp)/) || this.api.parameters.fields[i] == 'html' && this.api.parameters.formats.length > 0 && !this.api.parameters.formats.includes('html')) {\n console.log(this.api.parameters.fields[i] + ' is not included in the formats parameter.');\n }\n }\n }\n }\n }, {\n key: \"checkKeys\",\n value: function checkKeys() {\n var _this4 = this;\n\n if (!this.options.keys.every(function (elem) {\n return _this4.api.parameters.fields.indexOf(elem) > -1;\n })) {\n console.log('Not all keys are in fields. Please add them.');\n }\n\n ;\n }\n }, {\n key: \"validate\",\n value: function validate() {\n if (!this.checkGhostAPI() || !this.checkElements()) {\n return false;\n }\n\n ;\n\n if (this.development) {\n this.checkFields();\n this.checkFormats();\n this.checkKeys();\n }\n\n ;\n return true;\n }\n }, {\n key: \"init\",\n value: function init() {\n var _this5 = this;\n\n if (!this.validate()) {\n return;\n }\n\n if (this.trigger == 'focus') {\n document.querySelectorAll(this.input)[0].addEventListener('focus', function (e) {\n if (!_this5.check) {\n _this5.fetch();\n }\n\n ;\n });\n } else if (this.trigger == 'load') {\n window.onload = function () {\n if (!_this5.check) {\n _this5.fetch();\n }\n\n ;\n };\n }\n }\n }]);\n\n return GhostSearch;\n}();"],"file":"ghost-search.min.js"}
\ No newline at end of file
diff --git a/gulpfile.js b/gulpfile.js
new file mode 100644
index 0000000..0e234af
--- /dev/null
+++ b/gulpfile.js
@@ -0,0 +1,40 @@
+const gulp = require('gulp');
+const minify = require('gulp-minify');
+const babel = require('gulp-babel');
+const sourcemaps = require('gulp-sourcemaps');
+const resolveDependencies = require('gulp-resolve-dependencies');
+const concat = require('gulp-concat');
+const headerComment = require('gulp-header-comment');
+
+gulp.task('scripts', () =>
+ gulp.src('src/ghost-search.js')
+ .pipe(babel({
+ presets: ['@babel/env']
+ }))
+ .pipe(sourcemaps.init())
+ .pipe(resolveDependencies({
+ pattern: /\* @requires [\s-]*(.*\.js)/g
+ }))
+ .pipe(concat('ghost-search.js'))
+ .pipe(minify({
+ ext: {
+ min: '.min.js'
+ },
+ preserveComments: 'some'
+ }))
+ .pipe(headerComment(`
+ <%= pkg.name %> <%= pkg.version %> (<%= pkg.homepage %>)
+ <%= pkg.description %>
+ Copyright <%= moment().format('YYYY') %> Haunted Themes (<%= pkg.author.url %>)
+ Released under <%= pkg.license %> License
+ Released on: <%= moment().format('D MMM YYYY') %>
+ `))
+ .pipe(sourcemaps.write('/'))
+ .pipe(gulp.dest('dist'))
+);
+
+gulp.task('watch', () =>
+ gulp.watch('src/*.js', ['scripts'])
+);
+
+gulp.task('default', ['scripts', 'watch']);
\ No newline at end of file
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..858d32a
--- /dev/null
+++ b/package.json
@@ -0,0 +1,41 @@
+{
+ "name": "ghost-search",
+ "version": "0.1.0",
+ "description": "A simple but powerful search library for Ghost Blogging Platform.",
+ "main": "ghost-search.js",
+ "scripts": {
+ "dev": "gulp"
+ },
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/HauntedThemes/ghost-search.git"
+ },
+ "keywords": [
+ "ghost",
+ "search"
+ ],
+ "author": {
+ "name": "Haunted Themes",
+ "email": "contact@hauntedthemes.com",
+ "url": "https://www.hauntedthemes.com"
+ },
+ "license": "MIT",
+ "bugs": {
+ "url": "https://github.com/HauntedThemes/ghost-search/issues"
+ },
+ "homepage": "https://github.com/HauntedThemes/ghost-search",
+ "dependencies": {
+ "fuzzysort": "^1.1.4"
+ },
+ "devDependencies": {
+ "@babel/core": "^7.0.1",
+ "@babel/preset-env": "^7.0.0",
+ "gulp": "^3.9.1",
+ "gulp-babel": "^8.0.0",
+ "gulp-concat": "^2.6.1",
+ "gulp-header-comment": "^0.4.0",
+ "gulp-minify": "^3.1.0",
+ "gulp-resolve-dependencies": "^2.2.0",
+ "gulp-sourcemaps": "^2.6.4"
+ }
+}
diff --git a/src/ghost-search.js b/src/ghost-search.js
new file mode 100644
index 0000000..a322ec9
--- /dev/null
+++ b/src/ghost-search.js
@@ -0,0 +1,260 @@
+'use strict';
+
+/**
+ * @requires ../node_modules/fuzzysort/fuzzysort.js
+ */
+
+class GhostSearch {
+ constructor(args) {
+
+ this.check = false;
+
+ const defaults = {
+ input: '#ghost-search-field',
+ results: '#ghost-search-results',
+ button: '',
+ development: false,
+ template: function(result) {
+ let url = [location.protocol, '//', location.host].join('');
+ return '' + result.title + '';
+ },
+ trigger: 'focus',
+ options: {
+ keys: [
+ 'title'
+ ],
+ limit: 10,
+ threshold: -3500,
+ allowTypo: false
+ },
+ api: {
+ resource: 'posts',
+ parameters: {
+ limit: 'all',
+ fields: ['title', 'slug'],
+ filter: '',
+ include: '',
+ order: '',
+ formats: '',
+ },
+ },
+ on: {
+ beforeDisplay: function(){},
+ afterDisplay: function(results){},
+ beforeFetch: function(){},
+ afterFetch: function(results){}
+ }
+ }
+
+ const merged = this.mergeDeep(defaults, args);
+ Object.assign(this, merged);
+ this.init();
+
+ }
+
+ mergeDeep(target, source) {
+ if ((target && typeof target === 'object' && !Array.isArray(target) && target !== null) && (source && typeof source === 'object' && !Array.isArray(source) && source !== null)) {
+ Object.keys(source).forEach(key => {
+ if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key]) && source[key] !== null) {
+ if (!target[key]) Object.assign(target, { [key]: {} });
+ this.mergeDeep(target[key], source[key]);
+ } else {
+ Object.assign(target, { [key]: source[key] });
+ }
+ });
+ }
+ return target;
+ }
+
+ url(){
+
+ if (this.api.resource == 'posts' && this.api.parameters.include.match( /(tags|authors)/ )) {
+ delete this.api.parameters.fields;
+ };
+
+ let url = ghost.url.api(this.api.resource, this.api.parameters);
+
+ return url;
+
+ }
+
+ fetch(){
+
+ let url = this.url();
+
+ this.on.beforeFetch();
+
+ fetch(url)
+ .then(response => response.json())
+ .then(resource => this.search(resource))
+ .catch(error => console.error(`Fetch Error =\n`, error));
+
+ }
+
+ createElementFromHTML(htmlString) {
+ var div = document.createElement('div');
+ div.innerHTML = htmlString.trim();
+ return div.firstChild;
+ }
+
+ cleanup(input) {
+ return input.toLowerCase().replace(/[^a-zA-Z0-9]+/g, "-");
+ }
+
+ displayResults(data){
+
+ if (document.querySelectorAll(this.results)[0].firstChild !== null && document.querySelectorAll(this.results)[0].firstChild !== '') {
+ while (document.querySelectorAll(this.results)[0].firstChild) {
+ document.querySelectorAll(this.results)[0].removeChild(document.querySelectorAll(this.results)[0].firstChild);
+ }
+ };
+
+ let inputValue = document.querySelectorAll(this.input)[0].value;
+ const results = fuzzysort.go(inputValue, data, {
+ keys: this.options.keys,
+ limit: this.options.limit,
+ allowTypo: this.options.allowTypo,
+ threshold: this.options.threshold
+ });
+ for (let key in results){
+ if (key < results.length) {
+ document.querySelectorAll(this.results)[0].appendChild(this.createElementFromHTML(this.template(results[key].obj)));
+ };
+ }
+
+ this.on.afterDisplay(results)
+
+ }
+
+ search(resource){
+
+ let data = resource[this.api.resource];
+
+ this.on.afterFetch(data);
+ this.check = true;
+
+ if (this.button != '') {
+ let button = document.querySelectorAll(this.button)[0];
+ if (button.tagName == 'INPUT' && button.type == 'submit') {
+ button.closest('form').addEventListener("submit", e => {
+ e.preventDefault()
+ });
+ };
+ button.addEventListener('click', e => {
+ e.preventDefault()
+ this.on.beforeDisplay()
+ this.displayResults(data)
+ })
+ }else{
+ document.querySelectorAll(this.input)[0].addEventListener('keyup', e => {
+ this.on.beforeDisplay()
+ this.displayResults(data)
+ })
+ };
+
+ }
+
+ checkGhostAPI(){
+ if (typeof ghost === 'undefined') {
+ console.log('Ghost API is not enabled');
+ return false;
+ };
+ return true;
+ }
+
+ checkElements(){
+ if(!document.querySelectorAll(this.input).length){
+ console.log('Input not found.');
+ return false;
+ }
+ if(!document.querySelectorAll(this.results).length){
+ console.log('Results not found.');
+ return false;
+ };
+ if(this.button != ''){
+ if (!document.querySelectorAll(this.button).length) {
+ console.log('Button not found.');
+ return false;
+ };
+ }
+ return true;
+ }
+
+ checkFields(){
+
+ let validFields = [];
+
+ if (this.api.resource == 'posts') {
+ validFields = ['amp', 'authors', 'codeinjection_foot', 'codeinjection_head', 'comment_id', 'created_at', 'created_by', 'custom_excerpt', 'custom_template', 'feature_image', 'featured', 'html', 'id', 'locale', 'meta_description', 'meta_title', 'mobiledoc', 'og_description', 'og_image', 'og_title', 'page', 'plaintext', 'primary_author', 'primary_tag', 'published_at', 'published_by', 'slug', 'status', 'tags', 'title', 'twitter_description', 'twitter_image', 'twitter_title', 'updated_at', 'updated_by', 'url', 'uuid', 'visibility'];
+ }else if(this.api.resource == 'tags'){
+ validFields = ['count', 'created_at', 'created_by', 'description', 'feature_image', 'id', 'meta_description', 'meta_title', 'name', 'parent', 'slug', 'updated_at', 'updated_by', 'visibility']
+ }else if(this.api.resource == 'users'){
+ validFields = ['accessibility', 'bio', 'count', 'cover_image', 'facebook', 'id', 'locale', 'location', 'meta_description', 'meta_title', 'name', 'profile_image', 'slug', 'tour', 'twitter', 'visibility', 'website']
+ }
+
+ for (let i = 0; i < this.api.parameters.fields.length; i++) {
+ if (!validFields.includes(this.api.parameters.fields[i])) {
+ console.log('\'' + this.api.parameters.fields[i] + '\' is not a valid field for ' + this.api.resource + '. Valid fields for ' + this.api.resource + ': [\'' + validFields.join('\', \'') + '\']');
+ }
+ }
+
+ }
+
+ checkFormats(){
+ if (this.api.resource == 'posts' && (this.api.parameters.fields && typeof this.api.parameters.fields === 'object' && this.api.parameters.fields.constructor === Array)) {
+ for (let i = 0; i < this.api.parameters.fields.length; i++) {
+ if (
+ !this.api.parameters.formats.includes(this.api.parameters.fields[i]) && this.api.parameters.fields[i].match( /(plaintext|mobiledoc|amp)/ ) ||
+ (this.api.parameters.fields[i] == 'html' && this.api.parameters.formats.length > 0 && !this.api.parameters.formats.includes('html'))
+ ) {
+ console.log(this.api.parameters.fields[i] + ' is not included in the formats parameter.');
+ }
+ }
+ }
+ }
+
+ checkKeys(){
+ if (!this.options.keys.every(elem => this.api.parameters.fields.indexOf(elem) > -1)) {
+ console.log('Not all keys are in fields. Please add them.');
+ };
+ }
+
+ validate(){
+
+ if (!this.checkGhostAPI() || !this.checkElements()) {
+ return false;
+ };
+
+ if (this.development) {
+ this.checkFields();
+ this.checkFormats();
+ this.checkKeys();
+ };
+
+ return true;
+
+ }
+
+ init(){
+
+ if (!this.validate()) {
+ return;
+ }
+
+ if (this.trigger == 'focus') {
+ document.querySelectorAll(this.input)[0].addEventListener('focus', e => {
+ if (!this.check) {
+ this.fetch()
+ };
+ })
+ }else if(this.trigger == 'load'){
+ window.onload = () => {
+ if (!this.check) {
+ this.fetch()
+ };
+ }
+ }
+
+ }
+
+}
\ No newline at end of file