diff --git a/.eslintignore b/.eslintignore index a3c063fc..ec9ea4f0 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,3 +1,4 @@ coverage/ dist/ docs/ +dist-es5-module/ diff --git a/docs/documentation.md b/docs/documentation.md index b8851dea..c255b75f 100644 --- a/docs/documentation.md +++ b/docs/documentation.md @@ -20,6 +20,8 @@ you need to integrate your new search into your website. 2. We'll configure your search experience, 3. You'll need to add a bit of JavaScript and CSS code to your website. +If you prefer to DIY, you can run the [scraper][28] in your own infra. + ## Setup Once we've crawled your documentation website we'll send you the credentials you @@ -39,7 +41,7 @@ docsearch({ ## Customization -The default colorscheme is blue and gray: +The default colorscheme is white and gray: ![Default colorscheme][17] @@ -103,6 +105,7 @@ You can regenerate the whole final `css` file from those `scss` files by running All you have to do now is change the `link` tag that was loading the default styling from our CDN, to one that is loading your newly compiled file. + ## Custom options DocSearch is a wrapper around the [autocomplete.js][20] library that gets its @@ -137,6 +140,19 @@ search.autocomplete.on('autocomplete:opened', function(e) { }); ``` +We already bind the autocomplete:selected event inside the docsearch. +If you want to replace the default behavior you can pass the handleSelected option + +```javascript +var search = docsearch({ + apiKey: '', + indexName: '', + inputSelector: '', + handleSelected: function (input, event, suggestion) { + } +}); +``` + ### Algolia options You can also pass any specific option to the Algolia API to change the way @@ -163,7 +179,7 @@ You will find all Algolia API options in its [own documentation][23] [4]: https://img.shields.io/coveralls/algolia/docsearch/master.svg?style=flat-square [5]: http://img.shields.io/badge/license-MIT-green.svg?style=flat-square [6]: https://img.shields.io/npm/dm/docsearch.js.svg?style=flat-square -[7]: ./docs/img/showcase/example-eslint.gif +[7]: ./docs/img/showcase/example-apiary.gif [8]: #introduction [9]: #setup [10]: #customization @@ -173,7 +189,7 @@ You will find all Algolia API options in its [own documentation][23] [14]: #documentation-website [15]: #macos [16]: https://community.algolia.com/docsearch/ -[17]: https://community.algolia.com/docsearch/img/default-colorscheme.png +[17]: ./docs/img/default-colorscheme.png [18]: https://github.com/algolia/docsearch/blob/master/src/styles/_variables.scss [19]: https://github.com/algolia/docsearch/blob/master/src/styles/main.scss [20]: https://github.com/algolia/autocomplete.js @@ -184,4 +200,5 @@ You will find all Algolia API options in its [own documentation][23] [25]: https://jekyllrb.com/ [26]: https://www.ruby-lang.org/en/ [27]: http://bundler.io/ +[28]: https://github.com/algolia/docsearch-scraper diff --git a/src/lib/DocSearch.js b/src/lib/DocSearch.js index a08d0705..15640162 100644 --- a/src/lib/DocSearch.js +++ b/src/lib/DocSearch.js @@ -4,7 +4,7 @@ import autocomplete from 'autocomplete.js'; import templates from './templates.js'; import utils from './utils.js'; import version from './version.js'; -import $ from 'autocomplete.js/zepto.js'; +import $ from './zepto.js'; /** * Adds an autocomplete dropdown to an input field @@ -46,14 +46,16 @@ class DocSearch { enhancedSearchInput = false, layout = 'collumns' }) { - DocSearch.checkArguments({apiKey, indexName, inputSelector, debug, algoliaOptions, autocompleteOptions, transformData, handleSelected, enhancedSearchInput, layout}); + DocSearch.checkArguments({apiKey, indexName, inputSelector, debug, algoliaOptions, + autocompleteOptions, transformData, handleSelected, enhancedSearchInput, layout}); this.apiKey = apiKey; this.appId = appId; this.indexName = indexName; this.input = DocSearch.getInputFromSelector(inputSelector); this.algoliaOptions = {hitsPerPage: 5, ...algoliaOptions}; - let autocompleteOptionsDebug = autocompleteOptions && autocompleteOptions.debug ? autocompleteOptions.debug: false; + const autocompleteOptionsDebug = autocompleteOptions && autocompleteOptions.debug ? + autocompleteOptions.debug : false; autocompleteOptions.debug = debug || autocompleteOptionsDebug; this.autocompleteOptions = autocompleteOptions; this.autocompleteOptions.cssClasses = { @@ -68,7 +70,7 @@ class DocSearch { this.client.addAlgoliaAgent('docsearch.js ' + version); if (enhancedSearchInput) { - DocSearch.injectSearchBox(this.input, this); + DocSearch.injectSearchBox(this.input); this.input = DocSearch.getInputFromSelector('#docsearch'); } @@ -83,14 +85,14 @@ class DocSearch { this.autocomplete.on( 'autocomplete:selected', handleSelected.bind(null, this.autocomplete.autocomplete) - ) + ); this.autocomplete.on( 'autocomplete:shown', this.handleShown.bind(null, this.input) - ) + ); if (enhancedSearchInput) { - DocSearch.bindSearchBoxEvent(this.autocomplete); + DocSearch.bindSearchBoxEvent(); } } @@ -110,24 +112,24 @@ class DocSearch { } } - static injectSearchBox(input, docsearch) { + static injectSearchBox(input) { input.before(templates.searchBox); input.remove(); } - static bindSearchBoxEvent(autocomplete) { - $(".searchbox [type='reset']").on("click", function() { - $("input#docsearch").focus(); - $(this).addClass("hide"); - autocomplete.autocomplete.setVal(""); + static bindSearchBoxEvent() { + $('.searchbox [type="reset"]').on('click', function() { + $('input#docsearch').focus(); + $(this).addClass('hide'); + autocomplete.autocomplete.setVal(''); }); - $("input#docsearch").on("keyup", function() { - var searchbox = document.querySelector("input#docsearch"); - var reset = document.querySelector(".searchbox [type='reset']"); - reset.className = "searchbox__reset"; - if (searchbox.value.length === 0){ - reset.className += " hide"; + $('input#docsearch').on('keyup', function() { + var searchbox = document.querySelector('input#docsearch'); + var reset = document.querySelector('.searchbox [type="reset"]'); + reset.className = 'searchbox__reset'; + if (searchbox.value.length === 0) { + reset.className += ' hide'; } }); } @@ -148,6 +150,7 @@ class DocSearch { * Returns the `source` method to be passed to autocomplete.js. It will query * the Algolia index and call the callbacks with the formatted hits. * @function getAutocompleteSource + * @param {function} transformData An optional function to transform the hits * @returns {function} Method to be passed as the `source` option of * autocomplete */ @@ -181,30 +184,31 @@ class DocSearch { // Group hits by category / subcategory var groupedHits = utils.groupBy(hits, 'lvl0'); $.each(groupedHits, (level, collection) => { - let groupedHitsByLvl1 = utils.groupBy(collection, 'lvl1'); - let flattenedHits = utils.flattenAndFlagFirst(groupedHitsByLvl1, 'isSubCategoryHeader'); + const groupedHitsByLvl1 = utils.groupBy(collection, 'lvl1'); + const flattenedHits = utils.flattenAndFlagFirst(groupedHitsByLvl1, 'isSubCategoryHeader'); groupedHits[level] = flattenedHits; }); groupedHits = utils.flattenAndFlagFirst(groupedHits, 'isCategoryHeader'); // Translate hits into smaller objects to be send to the template return groupedHits.map((hit) => { - let url = DocSearch.formatURL(hit); - let category = utils.getHighlightedValue(hit, 'lvl0'); - let subcategory = utils.getHighlightedValue(hit, 'lvl1') || category; - let displayTitle = utils.compact([ + const url = DocSearch.formatURL(hit); + const category = utils.getHighlightedValue(hit, 'lvl0'); + const subcategory = utils.getHighlightedValue(hit, 'lvl1') || category; + const displayTitle = utils.compact([ utils.getHighlightedValue(hit, 'lvl2') || subcategory, utils.getHighlightedValue(hit, 'lvl3'), utils.getHighlightedValue(hit, 'lvl4'), utils.getHighlightedValue(hit, 'lvl5'), utils.getHighlightedValue(hit, 'lvl6') ]).join(''); - let text = utils.getSnippetedValue(hit, 'content'); - let isTextOrSubcatoryNonEmpty = (subcategory && subcategory != "") || (displayTitle && displayTitle != ""); - let isLvl1EmptyOrDuplicate = ! subcategory || subcategory == '' || subcategory == category; - let isLvl2 = displayTitle && displayTitle != '' && displayTitle != subcategory; - let isLvl1 = !isLvl2 && (subcategory && subcategory != '' && subcategory != category); - let isLvl0 = !isLvl1 && !isLvl2; + const text = utils.getSnippetedValue(hit, 'content'); + const isTextOrSubcatoryNonEmpty = (subcategory && subcategory !== '') || + (displayTitle && displayTitle !== ''); + const isLvl1EmptyOrDuplicate = !subcategory || subcategory === '' || subcategory === category; + const isLvl2 = displayTitle && displayTitle !== '' && displayTitle !== subcategory; + const isLvl1 = !isLvl2 && (subcategory && subcategory !== '' && subcategory !== category); + const isLvl0 = !isLvl1 && !isLvl2; return { isLvl0: isLvl0, @@ -245,7 +249,7 @@ class DocSearch { } static getSuggestionTemplate(isSimpleLayout) { - var stringTemplate = isSimpleLayout ? templates.suggestionSimple : templates.suggestion + const stringTemplate = isSimpleLayout ? templates.suggestionSimple : templates.suggestion; const template = Hogan.compile(stringTemplate); return (suggestion) => { return template.render(suggestion); @@ -257,20 +261,22 @@ class DocSearch { window.location.href = suggestion.url; } - handleShown(input, event) { - var middleOfInput = input.offset().left + input.width() / 2; - var middleOfWindow = $(document).width() / 2; + handleShown(input) { + const middleOfInput = input.offset().left + input.width() / 2; + let middleOfWindow = $(document).width() / 2; if (isNaN(middleOfWindow)) { middleOfWindow = 900; } - var alignClass = middleOfInput - middleOfWindow >= 0 ? 'algolia-autocomplete-right' : 'algolia-autocomplete-left'; - var otherAlignClass = middleOfInput - middleOfWindow < 0 ? 'algolia-autocomplete-right' : 'algolia-autocomplete-left'; + const alignClass = middleOfInput - middleOfWindow >= 0 ? + 'algolia-autocomplete-right' : 'algolia-autocomplete-left'; + const otherAlignClass = middleOfInput - middleOfWindow < 0 ? + 'algolia-autocomplete-right' : 'algolia-autocomplete-left'; - var autocompleteWrapper = $('.algolia-autocomplete'); - if (! autocompleteWrapper.hasClass(alignClass)) { - autocompleteWrapper.addClass(alignClass) + const autocompleteWrapper = $('.algolia-autocomplete'); + if (!autocompleteWrapper.hasClass(alignClass)) { + autocompleteWrapper.addClass(alignClass); } if (autocompleteWrapper.hasClass(otherAlignClass)) { diff --git a/src/lib/main.js b/src/lib/main.js index 27079cea..5cdd7755 100644 --- a/src/lib/main.js +++ b/src/lib/main.js @@ -2,7 +2,7 @@ import toFactory from 'to-factory'; import DocSearch from './DocSearch'; import version from './version.js'; -let docsearch = toFactory(DocSearch); +const docsearch = toFactory(DocSearch); docsearch.version = version; export default docsearch; diff --git a/src/lib/templates.js b/src/lib/templates.js index 724affa3..e37ebd9f 100644 --- a/src/lib/templates.js +++ b/src/lib/templates.js @@ -1,14 +1,15 @@ -let prefix = 'algolia-docsearch'; -let suggestionPrefix = `${prefix}-suggestion`; -let footerPrefix = `${prefix}-footer`; +const prefix = 'algolia-docsearch'; +const suggestionPrefix = `${prefix}-suggestion`; +const footerPrefix = `${prefix}-footer`; -let templates = { +/* eslint-disable max-len */ + +const templates = { suggestion: `
-
{{{category}}}
@@ -32,7 +33,6 @@ let templates = { {{#isSubCategoryHeader}}${suggestionPrefix}__secondary{{/isSubCategoryHeader}} suggestion-layout-simple "> -
{{^isLvl0}} {{{category}}} @@ -44,7 +44,6 @@ let templates = { {{/isLvl1EmptyOrDuplicate}} {{/isLvl1}} {{/isLvl0}} -
{{#isLvl2}} {{{title}}} diff --git a/src/lib/utils.js b/src/lib/utils.js index dddbd1cc..ffbdcdbc 100644 --- a/src/lib/utils.js +++ b/src/lib/utils.js @@ -1,4 +1,4 @@ -import $ from 'autocomplete.js/zepto.js'; +import $ from './zepto.js'; let utils = { /* diff --git a/src/lib/zepto.js b/src/lib/zepto.js new file mode 100644 index 00000000..42c779d4 --- /dev/null +++ b/src/lib/zepto.js @@ -0,0 +1,10 @@ +/* zepto work-around */ + +const current$ = window.$; +const currentZepto = window.Zepto; +require('autocomplete.js/zepto.js'); +const zepto = window.Zepto; +window.$ = current$; +window.Zepto = currentZepto; + +export default zepto; diff --git a/test/DocSearch-test.js b/test/DocSearch-test.js index f59aad06..9371b674 100644 --- a/test/DocSearch-test.js +++ b/test/DocSearch-test.js @@ -1,5 +1,6 @@ /* eslint-env mocha */ /* eslint no-new:0 */ +/* eslint-disable max-len */ import jsdom from 'mocha-jsdom'; import expect from 'expect'; @@ -17,7 +18,7 @@ describe('DocSearch', () => { // We need to load DocSearch from here as it depends on Zepto, which itself // needs jsdom to be called before being loaded. DocSearch = require('../src/lib/DocSearch.js'); - $ = require('npm-zepto'); + $ = require('../src/lib/zepto.js'); // Note: If you edit this HTML while doing TDD with `npm run test:watch`, // you will have to restart `npm run test:watch` for the new HTML to be @@ -168,7 +169,7 @@ describe('DocSearch', () => { // Then expect(typeof actual.algoliaOptions).toEqual('object'); expect(actual.algoliaOptions.anOption).toEqual(42); - expect(actual.autocompleteOptions).toEqual({debug: false, "cssClasses": { "prefix": "ds" }, anOption: 44}); + expect(actual.autocompleteOptions).toEqual({debug: false, cssClasses: {prefix: 'ds'}, anOption: 44}); }); it('should instantiate algoliasearch with the correct values', () => { // Given @@ -205,7 +206,7 @@ describe('DocSearch', () => { // Then expect(AutoComplete.calledOnce).toBe(true); - expect(AutoComplete.calledWith($input, {anOption: '44', "cssClasses": { "prefix": "ds" }, debug: false})).toBe(true); + expect(AutoComplete.calledWith($input, {anOption: '44', cssClasses: {prefix: 'ds'}, debug: false})).toBe(true); }); it('should listen to the selected and shown event of autocomplete', () => { // Given @@ -404,7 +405,6 @@ describe('DocSearch', () => { ds.autocomplete.trigger('autocomplete:shown'); expect($('.algolia-autocomplete').attr('class')).toEqual('algolia-autocomplete algolia-autocomplete-left'); - }); }); @@ -980,11 +980,11 @@ describe('DocSearch', () => { let actual = DocSearch.getSuggestionTemplate(); // When - actual({'foo': 'bar'}); + actual({foo: 'bar'}); // Then expect(render.calledOnce).toBe(true); - expect(render.args[0][0]).toEqual({'foo': 'bar'}); + expect(render.args[0][0]).toEqual({foo: 'bar'}); }); }); });