Use Algolia Docsearch index for Instantsearch.js section in Docusaurus page

956 views Asked by At

I have a documentation site built using Docusaurus.io, and am using Docsearch as my search in the navbar - this is working fine. However, I'd like to implement Algolia instantsearch on the landing page of the site as well. The code is working fine with an old index set up with instantsearch, but when I plug in the API key and ID of the new Docsearch index into the instantsearch config function, I get a stream of JSON objects that I can't make sense of.

This is the template I'm trying to use (which worked on my old index on a Jekyll site):

const hitTemplate = function(hit) {
    console.log(hit);
  let url = hit.url;
  const title = hit.lvl1;
  const content = hit._highlightResult.matchedWords;

  return `
    <div class="post-item">
      <a class="post-link" href="${url}">
        <h4>${title}</h4>
      </a>
      <div class="search-article-description">${content}</div>
      <a href="${url}" class="read-more">Read More &raquo;</a>
    </div>
  `;

That results in a blank <div>.

When no template is applied, my results come back as nested JSON objects.

What I'm trying to implement is an additional instantsearch that only appears on the homepage of the docs site. From what I understand, I can't have two input selectors in Docsearch, which is why I've been trying to use instantsearch. I need a template that works with the way the Docsearch API returns data.

1

There are 1 answers

0
bildungsroman On BEST ANSWER

After a week of trial and error and some very valuable help from the Docsearch devs via github, I came up with a solution that works for my documentation site, and am sharing it here in the hopes that other frustrated users of Docusaurus, Docsearch and Instantsearch find their way to this solution:

In instantsearch.js (found in website/static/js):

// instantSearch on homepage
document.addEventListener('DOMContentLoaded', () => {
  // Set initial parameters for instantsearch on the homepage
  const mainSearch = instantsearch({
    appId: '...',
    apiKey: '...',
    indexName: 'your-docsearch-index-name',
    searchParameters: {
      hitsPerPage: 10
    },
    routing: true
  });

  mainSearch.addWidget(
    instantsearch.widgets.configure({
      filters: 'version:1.5.5' // set latest version here
    })
  );

  // initialize SearchBox
  mainSearch.addWidget(
    instantsearch.widgets.searchBox({
      container: '#search_input_main',
      placeholder: 'Search the Docs',
      autofocus: false,
      poweredBy: true
    })
  );
  // add hits cont
  mainSearch.addWidget(
    instantsearch.widgets.hits({
      container: '#search-hits',
      templates: {
        empty: 'No results for the search term <em>"{{query}}"</em>.',
        item: $('#results-template').html() // this is the template at index.js
      },
      hitsPerPage: 10
    })
  );

  // add pagination
  mainSearch.addWidget(
    instantsearch.widgets.pagination({
      container: '#pagination-container',
      maxPages: 20,
      // default is to scroll to 'body', here we disable this behavior
      scrollTo: '#search-container'
    })
  );

  mainSearch.start();

  // Toggle search results
  const search_input_main = document.querySelector('#search_input_main');
  const searchContainer = document.querySelector('#search-container');

  // Bind keyup event on the input
  search_input_main.addEventListener('keyup', function() {
    if (search_input_main.value.length > 0) {
      searchContainer.classList.remove('hidden');
    } else {
      searchContainer.classList.add('hidden');
    }
  });

  // hide results on reset
  document.querySelector('.ais-search-box--reset').addEventListener('click', function() {
    searchContainer.classList.add('hidden');
  });

});

And this is my pages/en/index.js:

const React = require('react');
...
// for homepage instantsearch
let resultsTemplate = `
<script type="text/template" id="results-template">
  <div class="ais-result">
    {{#hierarchy.lvl0}}
    <div class="ais-lvl0">
      <a title="{{_highlightResult.hierarchy.lvl1.value}}" href="{{{url}}}"><h4>{{{_highlightResult.hierarchy.lvl0.value}}}</h4></a>
    </div>
    {{/hierarchy.lvl0}}

    <div class="ais-lvl1 breadcrumbs">
      {{#hierarchy.lvl1}} {{{_highlightResult.hierarchy.lvl1.value}}} {{/hierarchy.lvl1}} {{#hierarchy.lvl2}} > {{{_highlightResult.hierarchy.lvl2.value}}} {{/hierarchy.lvl2}} {{#hierarchy.lvl3}} > {{{_highlightResult.hierarchy.lvl3.value}}} {{/hierarchy.lvl3}}
      {{#hierarchy.lvl4}} > {{{_highlightResult.hierarchy.lvl4.value}}} {{/hierarchy.lvl4}}
    </div>

    <div class="ais-content">
      {{{#content}}} {{{_highlightResult.content.value}}} {{{/content}}}
    </div>
  </div>
</script>
`
...

const SearchInput = () => (
  <div>
    <div className='productShowcaseSection small-paddingBottom paddingTop' style={{textAlign: 'center'}}>
      <h3>Get the most out of Stackery's serverless toolkit</h3>
      <Container>
        <div className='search_input_div'>
          <input id="search_input_main" type="text" className="form-control" placeholder="Search the docs" aria-label="Search" aria-describedby="search"></input>
        </div>
      </Container>
    </div>
  </div>
);

const SearchHits = () => {
  return (
    <Container className='hidden' id='search-container'>
      <h3>Search results:</h3>
      <div id='search-hits'></div>
      <div id='pagination-container'></div>
      <div
        dangerouslySetInnerHTML={{ __html: resultsTemplate }}
      />
    </Container>
  )
}

class HomeSplash extends React.Component {
  render () {
    const language = this.props.language || '';
    return (
      <SplashContainer>
        <div className='inner'>
          <ProjectTitle />
          <PromoSection>
            ...
          </PromoSection>
          <SearchInput />
        </div>
      </SplashContainer>
    );
  }
}
...

class Index extends React.Component {
  render () {
    const language = this.props.language || '';

    return (
      <div>
        <HomeSplash language={language} />
        <SearchHits />
        <BrowseDocs />
        <TryIt />
      </div>
    );
  }
}

module.exports = Index;

Things to note:

  • Be sure to add filters: 'version:your-version' or the instantsearch will return results for every version you have, leading to tons of repetition.
  • The JQuery at the bottom of instantsearch.js is for displaying and hiding the <div> where your search results appear depending on a keyup event in the search imput
  • The reason I used const mainSearch = instantsearch({... instead of const search is that the default navbar Docsearch already uses the search variable. If you have more than one search on the page, you'll need to do this.
  • While this guide is Docusaurus-specific, this helpful JSfiddle shows how to implement instantsearch with a Docsearch index on a normal static site: https://jsfiddle.net/s_pace/965a4w3o/1/
  • Please comment if you need help implementing this on your own Docusaurus site, I'm happy to help