feat(main): Add main wrapper
This commit is contained in:
parent
d65cb99f9a
commit
bdac9dd817
8 changed files with 180 additions and 7375 deletions
|
|
@ -1,7 +1,7 @@
|
|||
import documentationSearch from '../index.js';
|
||||
|
||||
documentationSearch({
|
||||
apiKey: 'aaa',
|
||||
indexName: 'bbb',
|
||||
inputSelector: 'ccc'
|
||||
apiKey: '7cad300559017ed67717c9c6cba2666d',
|
||||
indexName: 'stripe',
|
||||
inputSelector: '#search-input'
|
||||
});
|
||||
|
|
|
|||
12
index.js
12
index.js
|
|
@ -1,11 +1 @@
|
|||
/* global google */
|
||||
|
||||
function documentationSearch({
|
||||
apiKey,
|
||||
indexName,
|
||||
inputSelector
|
||||
}) {
|
||||
console.info(apiKey, indexName, inputSelector);
|
||||
}
|
||||
|
||||
export default documentationSearch;
|
||||
module.exports = require('./src/lib/main.js');
|
||||
|
|
|
|||
7360
npm-shrinkwrap.json
generated
7360
npm-shrinkwrap.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -36,6 +36,11 @@
|
|||
},
|
||||
"peerDependencies": {},
|
||||
"dependencies": {
|
||||
"lodash": "^3.10.1"
|
||||
"algoliasearch": "^3.9.2",
|
||||
"autocomplete.js": "^0.13.0",
|
||||
"hogan.js": "^3.0.2",
|
||||
"lodash": "^3.10.1",
|
||||
"npm-zepto": "^1.1.7",
|
||||
"to-factory": "^1.0.0"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
129
src/lib/DocumentationSearch.js
Normal file
129
src/lib/DocumentationSearch.js
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
import Hogan from 'hogan.js';
|
||||
import algoliasearch from 'algoliasearch';
|
||||
import autocomplete from 'autocomplete.js';
|
||||
import groupBy from 'lodash/collection/groupBy';
|
||||
import templates from './templates.js';
|
||||
import utils from './utils.js';
|
||||
import $ from 'npm-zepto';
|
||||
|
||||
/**
|
||||
* Adds an autocomplete dropdown to an input fields
|
||||
* @function documentationSearch
|
||||
* @param {string} options.apiKey Read-only API key
|
||||
* @param {string} options.indexName Name of the index to target
|
||||
* @param {string} options.inputSelector CSS selector that targets the input
|
||||
* @param {Object} [options.algoliaOptions] Options to pass the underlying Algolia client
|
||||
* @param {Object} [options.autocompleteOptions] Options to pass to the underlying autocomplete instance
|
||||
* @return {Object}
|
||||
*/
|
||||
const usage = `Usage:
|
||||
documentationSearch({
|
||||
apiKey,
|
||||
indexName,
|
||||
inputSelector,
|
||||
[ options.{hint,debug} ]
|
||||
})`;
|
||||
class DocumentationSearch {
|
||||
constructor({
|
||||
apiKey,
|
||||
indexName,
|
||||
inputSelector,
|
||||
algoliaOptions = {
|
||||
hitsPerPage: 5
|
||||
},
|
||||
autocompleteOptions = {
|
||||
debug: true,
|
||||
hint: false
|
||||
}
|
||||
}) {
|
||||
this.checkArguments({apiKey, indexName, inputSelector, algoliaOptions, autocompleteOptions});
|
||||
|
||||
this.client = algoliasearch('BH4D9OD16A', this.apiKey);
|
||||
this.autocomplete = autocomplete(this.input, autocompleteOptions, [{
|
||||
source: this.getSource(),
|
||||
templates: {
|
||||
suggestion: this.getSuggestionTemplate()
|
||||
}
|
||||
}]);
|
||||
this.autocomplete.on('autocomplete:selected', this.handleSelected);
|
||||
}
|
||||
|
||||
// TEST:
|
||||
// - Usage error if no apiKey or no indexName
|
||||
// - Error if nothing matches
|
||||
// - Error if matches are not input
|
||||
// - apiKey nad indexName are set
|
||||
// - input is set to the Zepto-wrapped inputSelector
|
||||
checkArguments(args) {
|
||||
if (!args.apiKey || !args.indexName) {
|
||||
throw new Error(usage);
|
||||
}
|
||||
|
||||
const input = $(args.inputSelector).filter('input');
|
||||
if (input.length === 0) {
|
||||
throw new Error(`Error: No input element in the page matches ${args.inputSelector}`);
|
||||
}
|
||||
|
||||
this.apiKey = args.apiKey;
|
||||
this.indexName = args.indexName;
|
||||
this.input = input;
|
||||
this.algoliaOptions = args.algoliaOptions;
|
||||
this.autocompleteOptions = args.autocompleteOptions;
|
||||
}
|
||||
|
||||
// Returns a `source` method to be used by `autocomplete`. This will query the
|
||||
// Algolia index.
|
||||
getSource() {
|
||||
return (query, callback) => {
|
||||
this.client.search([{
|
||||
indexName: this.indexName,
|
||||
query: query,
|
||||
params: this.algoliaOptions
|
||||
}]).then((data) => {
|
||||
callback(this.formatHits(data.results[0].hits));
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
// Given a list of hits returned by the API, will reformat them to be used in
|
||||
// a Hogan template
|
||||
formatHits(hits) {
|
||||
// Group hits by category / subcategory
|
||||
var groupedHits = groupBy(hits, 'category');
|
||||
groupedHits.each((list, category) => {
|
||||
var groupedHitsBySubCategory = groupBy(list, 'subcategory');
|
||||
var flattenedHits = utils.flattenObject(groupedHitsBySubCategory, 'isSubcategoryHeader');
|
||||
groupedHits[category] = flattenedHits;
|
||||
});
|
||||
groupedHits = utils.flattenObject(groupedHits, 'isCategoryHeader');
|
||||
|
||||
// Translate hits into smaller objects to be send to the template
|
||||
return groupedHits.map((hit) => {
|
||||
return {
|
||||
isCategoryHeader: hit.isCategoryHeader,
|
||||
isSubcategoryHeader: hit.isSubcategoryHeader,
|
||||
category: hit._highlightResult.category ? hit._highlightResult.category.value : hit.category,
|
||||
subcategory: hit._highlightResult.subcategory ? hit._highlightResult.subcategory.value : hit.category,
|
||||
title: hit._highlightResult.display_title ? hit._highlightResult.display_title.value : hit.display_title,
|
||||
text: hit._snippetResult ? hit._snippetResult.text.value : hit.text,
|
||||
url: hit.url
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
getSuggestionTemplate() {
|
||||
const template = Hogan.compile(templates.suggestion);
|
||||
return (suggestion) => {
|
||||
return template.render(suggestion);
|
||||
};
|
||||
}
|
||||
|
||||
handleSelected(event, suggestion) {
|
||||
this.autocomplete.autocomplete.setVal('');
|
||||
window.location.href = suggestion.url;
|
||||
}
|
||||
}
|
||||
|
||||
export default DocumentationSearch;
|
||||
|
||||
|
||||
6
src/lib/main.js
Normal file
6
src/lib/main.js
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
import toFactory from 'to-factory';
|
||||
import DocumentationSearch from './DocumentationSearch';
|
||||
|
||||
let documentationSearch = toFactory(DocumentationSearch);
|
||||
|
||||
export default documentationSearch;
|
||||
20
src/lib/templates.js
Normal file
20
src/lib/templates.js
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
let prefix = 'documentationsearch--suggestion';
|
||||
|
||||
let templates = {
|
||||
suggestion: `
|
||||
<div class="${prefix} {{#isCategoryHeader}}${prefix}__main{{/isCategoryHeader}} {{#isSubcategoryHeader}}${prefix}__secondary{{/isSubcategoryHeader}}">
|
||||
<div class="${prefix}--category-header">{{{category}}}</div>
|
||||
<div class="${prefix}--wrapper">
|
||||
<div class="${prefix}--subcategory">{{{subcategory}}}</div>
|
||||
<div class="${prefix}--content">
|
||||
<div class="${prefix}--subcategory">{{{subcategory}}}</div>
|
||||
<div class="${prefix}--title">{{{title}}}</div>
|
||||
<div class="${prefix}--text">{{{text}}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
};
|
||||
|
||||
export default templates;
|
||||
|
||||
15
src/lib/utils.js
Normal file
15
src/lib/utils.js
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
import values from 'lodash/object/values';
|
||||
import flatten from 'lodash/array/flatten';
|
||||
|
||||
let utils = {
|
||||
// Flatten all values into one array, marking the first element with
|
||||
// `flagName`
|
||||
flattenObject(o, flagName) {
|
||||
values(o).map((value) => {
|
||||
value[0][flagName] = true;
|
||||
});
|
||||
return flatten(values);
|
||||
}
|
||||
};
|
||||
|
||||
export default utils;
|
||||
Loading…
Reference in a new issue