text: ---
  ---
  (function () {
      // Search Box Element
      // =============================================================================
      var siteSearchElement = document.getElementsByClassName('search-box')[0]
      var searchBoxElement = document.getElementById('search-box')
      var clearButton = document.getElementsByClassName('clear-button')[0]
      var main = document.getElementsByTagName('main')[0]
      var searchFilter = document.getElementsByClassName('search-filter')[0]
      var searchResults = document.getElementsByClassName('search-results')[0]
  
      searchBoxElement.oninput = function (event) {
          if (searchBoxElement.value && searchBoxElement.value.trim().length > 0) {
              siteSearchElement.classList.add('filled')
              onSearchChangeDebounced()
          } else {
              siteSearchElement.classList.remove('filled')
              onSearchChange()
          }
      }
  
      searchBoxElement.onfocus = function () {
          siteSearchElement.classList.add('focused')
          if (siteSearchElement.classList.contains('filled')) {
              searchResults.classList.add('visible')
          }
      }
  
      searchBoxElement.onblur = function() {
          siteSearchElement.classList.remove('focused')
      }
  
      document.body.addEventListener('click', function (event) {
          var target = event.target
          if (target.id !== 'search-box' && !target.classList.contains('search-btn') && !target.parentNode.classList.contains('search-btn')) {
              searchResults.classList.remove('visible')
          }
      })
  
      clearButton.onclick = function () {
          searchBoxElement.value = ''
          searchBoxElement.dispatchEvent(new Event('input', {
              'bubbles': true,
              'cancelable': true
          }))
      }
  
      // Assign search endpoint based on env config
      // ===========================================================================
      var endpoint = null
      var env = '{{ jekyll.environment }}'
      var elasticSearchIndex = '{{site.github.owner_name}}-{{site.github.repository_name}}'
  
      if (env === 'production') {
          endpoint = '{{ site.server_PROD | append: '/' }}' + elasticSearchIndex
      } else {
          //  Allow overriding of search index in dev env
          var configElasticSearchIndex = '{{site.elastic_search_index}}'
          if (configElasticSearchIndex) {
              elasticSearchIndex = configElasticSearchIndex
          }
          endpoint = '{{ site.server_DEV | append: '/' }}' + elasticSearchIndex
      }
  
      var search_endpoint = endpoint + '/search'
  
  
      // Global Variables
      // =============================================================================
  
      var wordsToHighlight = []
      var sectionIndex = {}
      var minQueryLength = 3
      var lunrIndex = null
      // Begin Lunr Indexing
      // =============================================================================
      function getLunrIndex() {
          return fetch('{{ "/assets/lunrIndex.json" | relative_url }}')
              .then(function (res) {
                      return res.json()
              })
              .then(function (json) {
                  lunrIndex = lunr.Index.load(json.index)
                  lunrIndex.pipeline.remove(lunr.stemmer)
                  sectionIndex = json.sectionIndex
              })
              .catch(function (err) {
                  console.error('Fetch failed to read the Lunr index: ' + err)
              })
      }
  
      // Load Lunr Index if set
      // ============================================================================
      var searchSetOffline = "{{ site.offline }}" === "true" || false
  
      if (searchSetOffline) {
          getLunrIndex()
      }
  
      // Search
      // =============================================================================
      // Helper function to translate lunr search results
      // Returns a simple { title, content, link } array
      var snippetSpace = 40
      var maxSnippets = 4
      var maxResults = 10
      var translateLunrResults = function (allLunrResults) {
          var lunrResults = allLunrResults.slice(0, maxResults)
          return lunrResults.map(function (result) {
              var matchedDocument = sectionIndex[result.ref]
              var contentSnippets = []
              var titleSnippets = []
              var snippetsRangesByFields = {}
              // Loop over matching terms
              var rangesByFields = {}
              // Group ranges according to field type(text / title)
              for (var term in result.matchData.metadata) {
                  // To highlight the main body later
                  wordsToHighlight.push(term)
                  var fields = result.matchData.metadata[term]
                  for (var field in fields) {
                      positions = fields[field].position
                      rangesByFields[field] = rangesByFields[field] ? rangesByFields[field].concat(positions) : positions
                  }
              }
              var snippetCount = 0
              // Sort according to ascending snippet range
              for (var field in rangesByFields) {
                  var ranges = rangesByFields[field]
                      .map(function (a) {
                          return [a[0] - snippetSpace, a[0] + a[1] + snippetSpace, a[0], a[0] + a[1]]
                      })
                      .sort(function (a, b) {
                          return a[0] - b[0]
                      })
                  // Merge contiguous ranges
                  var startIndex = ranges[0][0]
                  var endIndex = ranges[0][1]
                  var mergedRanges = []
                  var highlightRanges = []
                  for (rangeIndex in ranges) {
                      var range = ranges[rangeIndex]
                      snippetCount++
                      if (range[0] <= endIndex) {
                          endIndex = Math.max(range[1], endIndex)
                          highlightRanges = highlightRanges.concat([range[2], range[3]])
                      } else {
                          mergedRanges.push([startIndex].concat(highlightRanges).concat([endIndex]))
                          startIndex = range[0]
                          endIndex = range[1]
                          highlightRanges = [range[2], range[3]]
                      }
                      if (snippetCount >= maxSnippets) {
                          mergedRanges.push([startIndex].concat(highlightRanges).concat([endIndex]))
                          snippetsRangesByFields[field] = mergedRanges
                          break
                      }
                      if (+rangeIndex === ranges.length - 1) {
                          if (snippetCount + 1 < maxSnippets) {
                              snippetCount++
                          }
                          mergedRanges.push([startIndex].concat(highlightRanges).concat([endIndex]))
                          snippetsRangesByFields[field] = mergedRanges
                          if (snippetCount >= maxSnippets) {
                              break
                          }
                      }
                  }
              }
              // Extract snippets and add highlights to search results
              for (var field in snippetsRangesByFields) {
                  positions = snippetsRangesByFields[field]
                  positions.forEach(function (position) {
                      matchedText = matchedDocument[field]
                      var snippet = ''
                      // If start of matched text dont use ellipsis
                      if (position[0] > 0) {
                          snippet += '...'
                      }
                      snippet += matchedText.substring(position[0], position[1])
                      for (var i = 1; i <= position.length - 2; i++) {
                          if (i % 2 == 1) {
                              snippet += '<mark>'
                          } else {
                              snippet += '</mark>'
                          }
                          snippet += matchedText.substring(position[i], position[i + 1])
                      }
                      if (field === 'title') {
                          titleSnippets.push(snippet)
                      } else {
                          snippet += '...'
                          contentSnippets.push(snippet)
                      }
                  })
              }
              var joinHighlights = function (str) {
                  if (str) {
                      return str.replace(/<\/mark> <mark>/g, ' ')
                  }
              }
              // Build a simple flat object per lunr result
              return {
                  title: joinHighlights(titleSnippets.length === 0 ? matchedDocument.title: titleSnippets.join(' ')),
                  documentTitle: joinHighlights(matchedDocument.documentTitle),
                  content: joinHighlights(contentSnippets.join(' ')),
                  url: matchedDocument.url
              }
          })
      }
  
      // Displays the search results in HTML
      // Takes an array of objects with "title" and "content" properties
      var renderSearchResultsFromLunr = function (searchResults) {
          var container = document.getElementsByClassName('search-results')[0]
          container.scrollTop = 0
          container.innerHTML = ''
          if (!searchResults || searchResults.length === 0) {
              var error = generateErrorHTML()
              container.append(error)
          } else {
              searchResults.forEach(function (result, i) {
                  var element = generateResultHTML(result, i)
                  container.appendChild(element)
              })
          }
      }
  
      var renderSearchResultsFromServer = function (searchResults) {
          var container = document.getElementsByClassName('search-results')[0]
          container.scrollTop = 0
          container.innerHTML = ''
          if (typeof searchResults.hits === 'undefined') {
              var error = document.createElement('p')
              error.classList.add('not-found')
              error.innerHTML = searchResults
              container.appendChild(error)
              // Check if there are hits and max_score is more than 0 
              // Max score is checked as well as filter will always return something
          } else if (searchResults.hits.hits.length === 0 || searchResults.hits['max_score'] === 0) {
              var error = generateErrorHTML()
              container.appendChild(error)
          } else {
              searchResults.hits.hits.forEach(function (result, i) {
                  if (result._score) {
                      var formatted = formatResult(result, i)
                      var element = generateResultHTML(formatted)
                      container.appendChild(element)
                  }
              });
              highlightBody()
          }
      }
  
      var generateErrorHTML = function () {
          var error = document.createElement('p')
          error.innerHTML = 'Results matching your query were not found.'
          error.classList.add('not-found')
          return error
      }
  
      var generateResultHTML = function (result, i) {
          var element = document.createElement('a')
          element.className = 'search-link nav-link'
          var urlParts = result.url.split('/')
          urlParts = urlParts.filter(function (part) {
              return part !== ''
          })
          element.href = '/' + urlParts.join('/')
          var searchResult = document.createElement('div')
          var searchTitle = document.createElement('p')
          searchTitle.className = 'search-title'
          searchTitle.innerHTML = result.documentTitle || {{ site.title | jsonify }}
          searchResult.appendChild(searchTitle)
          var searchSubtitle = document.createElement('p')
          searchSubtitle.className = 'search-subtitle'
          searchSubtitle.innerHTML = result.title
          searchResult.appendChild(searchSubtitle)
          var searchContent = document.createElement('p')
          searchContent.className = 'search-content'
          searchContent.innerHTML = result.content
          searchResult.appendChild(searchContent)
          element.onmouseup = function() {
              searchResults.classList.remove('visible')
          }
          element.appendChild(searchResult)
          return element
      }
  
      formatResult = function (result) {
          var content = null
          var title = result._source.title
          var url = result._source.url;
          var documentTitle = result._source.documentTitle;
          var regex = /<mark>(.*?)<\/mark>/g
          var joinHighlights = function (str) {
              if (str) {
                  return str.replace(/<\/mark> <mark>/g, ' ')
              }
          }
          if (result.highlight) {
              ['title', 'content'].forEach(function (field) {
                  var curr, match, term;
                  if (result.highlight[field]) {
                      var curr = result.highlight[field].join('...')
                      //  trimLeft not supported in IE
                      var curr = curr.replace(/^\s+/, "")
                      var curr = joinHighlights(curr)
                      var match = true
                      while (match) {
                          match = regex.exec(curr)
                          if (match) {
                              var term = match[1].toLowerCase()
                              if ((wordsToHighlight.indexOf(term)) < 0) {
                                  wordsToHighlight.push(term)
                              }
                          }
                      }
                  }
              })
              if (result.highlight.content) {
                  content = joinHighlights(result.highlight.content.slice(0, Math.min(3, result.highlight.content.length)).join('...'))
              }
              if (result.highlight.title) {
                  title = joinHighlights(result.highlight.title[0])
              }
          }
          return {
              url: url,
              content: content ? '...' + content + '...' : '',
              title: title,
              documentTitle: documentTitle
          }
      }
  
      var debounce = function (func, threshold, execAsap) {
          var timeout = null;
          return function () {
              var args = 1 <= arguments.length ? slice.call(arguments, 0) : []
              obj = this
              var delayed = function () {
                  if (!execAsap) {
                      func.apply(obj, args)
                  }
                  timeout = null
              }
              if (timeout) {
                  clearTimeout(timeout)
              } else if (execAsap) {
                  func.apply(obj, args)
              }
              timeout = setTimeout(delayed, threshold || 100)
          }
      }
  
  
      var createEsQuery = function (queryStr) {
          var source = ['title', 'url', 'documentTitle']
          var title_automcomplete_q = {
              'match_phrase_prefix': {
                  'title': {
                      'query': queryStr,
                      'max_expansions': 20,
                      'boost': 100,
                      'slop': 10
                  }
              }
          }
          var content_automcomplete_q = {
              'match_phrase_prefix': {
                  'content': {
                      'query': queryStr,
                      'max_expansions': 20,
                      'boost': 60,
                      'slop': 10
                  }
              }
          }
          var title_keyword_q = {
              'match': {
                  'title': {
                      'query': queryStr,
                      'fuzziness': 'AUTO',
                      'max_expansions': 10,
                      'boost': 20,
                      'analyzer': 'stop'
                  }
              }
          }
          var content_keyword_q = {
              'match': {
                  'content': {
                      'query': queryStr,
                      'fuzziness': 'AUTO',
                      'max_expansions': 10,
                      'analyzer': 'stop'
                  }
              }
          }
  
          var bool_q = {
              'bool': {
                  'should': [title_automcomplete_q, content_automcomplete_q, title_keyword_q, content_keyword_q],
              }
          }
  
          // If document filter is present
          var page = pageIndex[window.location.pathname]
          if (!searchFilter.classList.contains('hidden') && page && page.documentInfo[0]) {
              // documentId is the alphanumeric and lowercase version of document title
              // used as a keyword filter to search within the document
              var documentId = page.documentInfo[0].replace(/[^\w]/g, '').toLowerCase()
              var filter_by_document = {
                  'term': {
                      'documentId': documentId
                  }
              }
              bool_q.bool.filter = filter_by_document
          }
  
          var highlight = {}
          highlight.require_field_match = false
          highlight.fields = {}
          highlight.fields['content'] = {
              'fragment_size': 80,
              'number_of_fragments': 6,
              'pre_tags': ['<mark>'],
              'post_tags': ['</mark>']
          }
          highlight.fields['title'] = {
              'fragment_size': 80,
              'number_of_fragments': 6,
              'pre_tags': ['<mark>'],
              'post_tags': ['</mark>']
          }
          return {
              '_source': source,
              'query': bool_q,
              'highlight': highlight
          }
      }
  
      // Call the API
      esSearch = function (query) {
          var esQuery = createEsQuery(query)
          fetch(search_endpoint, {
              method: 'POST',
              headers: {
                  'Content-Type': 'application/json'
              },
              body: JSON.stringify(esQuery)
          })
              .then(checkStatus)
              .then(parseJSON)
              .then(function (data) {
                  renderSearchResultsFromServer(data.body)
              })
              .catch(function (err) {
                  console.error(err)
                  renderSearchResultsFromServer('Failed to fetch search results')
              })
      }
  
      var lunrSearch = function (query) {
          // Add wildcard before and after
          var queryTerm = refineLunrSearchQuery(query)
          if (lunrIndex !== null) {
              var lunrResults = lunrIndex.search(queryTerm)
              var results = translateLunrResults(lunrResults)
              highlightBody()
              renderSearchResultsFromLunr(results)
          }
      }
  
      var refineLunrSearchQuery = function(query) {
          FUZZY_FACTOR = 4 // range: 1 to INF. Lower is fuzzier *relative to term length*.
          var addFuzzyOperator = function(term, fuzziness) {
              return term + '~' + 
                  Math.floor(term.length / Math.max(1, fuzziness)).toString()
          }
          var stringIsLettersOnly = function(str) {
              return /^[a-zA-Z]+$/.test(str)
          }
  
          var terms = query.split(' ')
          terms = terms.map(function(term) {
              if (stringIsLettersOnly(term)) {
                  return addFuzzyOperator(term, FUZZY_FACTOR)
              }           
              return term
          })
          return terms.join(' ')
      }
  
  
      var onSearchChange = function () {
          var query = searchBoxElement.value.trim()
          // Clear highlights
          wordsToHighlight = []
          if (query.length < minQueryLength) {
              searchResults.classList.remove('visible')
              highlightBody()
              return
          }
          searchResults.classList.add('visible')
  
          if (searchSetOffline) {
              lunrSearch(query)
          } else {
              esSearch(query)
              if (env === 'production' && window.ga) {
                  window.ga('send', 'pageview', '/search?query=' + encodeURIComponent(query))
              }
          }
      }
  
      var onSearchChangeDebounced = debounce(onSearchChange, 500, false)
  
      var isBackspaceFirstPress = true
      var isBackspacePressedOnEmpty = false
      // Detect that backspace is not part of longpress
      searchBoxElement.onkeydown = function (e) {
          searchResults.classList.remove('hidden')
          if (isBackspaceFirstPress && e.keyCode === 8) {
              isBackspaceFirstPress = false
              if (searchBoxElement.value === '') {
                  isBackspacePressedOnEmpty = true
              }
          }
      }
  
      clearSearchFilter = function () {
          searchFilter.classList.add('hidden')
      }
  
      searchFilter.onclick = clearSearchFilter
  
      searchBoxElement.onkeyup = function (e) {
          // flash search results on enter
          if (e.keyCode === 13) {
              var container = document.getElementsByClassName('search-results')[0]
              container.style.opacity = 0
              return setTimeout(function () {
                  return container.style.opacity = 1
              }, 100)
          }
          // Delete filter on backspace when input is empty and not part of longpress
          if (e.keyCode === 8) {
              isBackspaceFirstPress = true
              if (searchBoxElement.value === '' && isBackspacePressedOnEmpty) {
                  clearSearchFilter()
                  isBackspacePressedOnEmpty = false
                  return
              }
          }
      }
  
  
      // Highlighting
      // ============================================================================
      window.highlightBody = function () {
          // Check if Mark.js script is already imported
          if (Mark) {
              var instance = new Mark(main)
              instance.unmark()
              if (wordsToHighlight.length > 0) {
                  instance.mark(wordsToHighlight, {
                      exclude: ['h1'],
                      accuracy: {
                          value: 'exactly',
                          limiters: [',', '.', '(', ')', '-', '\'', '[', ']', '?', '/', '\\', ':', '*', '!', '@', '&']
                      },
                      separateWordSearch: false
                  })
              }
          }
      }
  
  })()
  