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';
|
import documentationSearch from '../index.js';
|
||||||
|
|
||||||
documentationSearch({
|
documentationSearch({
|
||||||
apiKey: 'aaa',
|
apiKey: '7cad300559017ed67717c9c6cba2666d',
|
||||||
indexName: 'bbb',
|
indexName: 'stripe',
|
||||||
inputSelector: 'ccc'
|
inputSelector: '#search-input'
|
||||||
});
|
});
|
||||||
|
|
|
||||||
12
index.js
12
index.js
|
|
@ -1,11 +1 @@
|
||||||
/* global google */
|
module.exports = require('./src/lib/main.js');
|
||||||
|
|
||||||
function documentationSearch({
|
|
||||||
apiKey,
|
|
||||||
indexName,
|
|
||||||
inputSelector
|
|
||||||
}) {
|
|
||||||
console.info(apiKey, indexName, inputSelector);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default documentationSearch;
|
|
||||||
|
|
|
||||||
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": {},
|
"peerDependencies": {},
|
||||||
"dependencies": {
|
"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