diff --git a/src/css/footer.css b/src/css/footer.css index dda2c03..46bd68b 100644 --- a/src/css/footer.css +++ b/src/css/footer.css @@ -25,7 +25,7 @@ grid-template-rows: auto 1fr auto; font-size: 14px; background-color: #f4f4f4; - align-items: start; + align-items: flex-start; } #footer-logo { diff --git a/src/js/vendor/docsearch.bundle.js b/src/js/vendor/docsearch.bundle.js index c5d8266..ede9e70 100644 --- a/src/js/vendor/docsearch.bundle.js +++ b/src/js/vendor/docsearch.bundle.js @@ -1,310 +1,471 @@ ;(function () { - 'use strict' + 'use strict' + + var FORWARD_BACK_TYPE = 2 + var CTRL_KEY_CODE = 17 + var LT_KEY_CODE = 188 + var S_KEY_CODE = 83 + var SOLIDUS_KEY_CODE = 191 + var SAVED_SEARCH_STATE_KEY = 'docs:saved-search-state' + var SAVED_SEARCH_STATE_VERSION = '2' + + var abc = require('docsearch.js/dist/cdn/docsearch.js') + // activateSearch(require('docsearch.js/dist/cdn/docsearch.js'), document.getElementById('search-script').dataset) - var FORWARD_BACK_TYPE = 2 - var CTRL_KEY_CODE = 17 - var LT_KEY_CODE = 188 - var S_KEY_CODE = 83 - var SOLIDUS_KEY_CODE = 191 - var SEARCH_FILTER_ACTIVE_KEY = 'docs:search-filter-active' - var SAVED_SEARCH_STATE_KEY = 'docs:saved-search-state' - var SAVED_SEARCH_STATE_VERSION = '1' - - activateSearch(require('docsearch.js/dist/cdn/docsearch.js'), document.getElementById('search-script').dataset) - - function activateSearch (docsearch, config) { - appendStylesheet(config.stylesheet) - var baseAlgoliaOptions = { - hitsPerPage: parseInt(config.pageSize) || 20, // cannot exceed the hitsPerPage value defined on the index - } - var searchField = document.getElementById(config.searchFieldId || 'search') - searchField.appendChild(Object.assign(document.createElement('div'), { className: 'algolia-autocomplete-results' })) - var controller = docsearch({ - appId: config.appId, - apiKey: config.apiKey, - indexName: config.indexName, - inputSelector: '#' + searchField.id + ' .query', - autocompleteOptions: { - autoselect: false, - debug: true, - hint: false, - minLength: 2, - appendTo: '#' + searchField.id + ' .algolia-autocomplete-results', - autoWidth: false, - templates: { - footer: - '', - }, + + function activateSearch (docsearch, config) { + appendStylesheet(config.stylesheet) + var baseAlgoliaOptions = { hitsPerPage: parseInt(config.pageSize) || 25 } + var searchField = document.getElementById(config.searchFieldId || 'search') + searchField.appendChild(Object.assign(document.createElement('div'), { className: 'algolia-autocomplete-results' })) + var controller = docsearch({ + appId: config.appId, + apiKey: config.apiKey, + indexName: config.indexName, + inputSelector: '#' + searchField.id + ' .query', + autocompleteOptions: { + autoselect: false, + debug: true, + hint: false, + minLength: 2, + appendTo: '#' + searchField.id + ' .algolia-autocomplete-results', + autoWidth: false, + templates: { + footer: + '
' + + '', }, - baseAlgoliaOptions: baseAlgoliaOptions, - }) - var input = controller.input - var typeahead = input.data('aaAutocomplete') - var dropdown = typeahead.dropdown - var menu = dropdown.$menu - var dataset = dropdown.datasets[0] - dataset.cache = false - dataset.source = controller.getAutocompleteSource(undefined, processQuery.bind(typeahead, controller)) - delete dataset.templates.footer - controller.queryDataCallback = processQueryData.bind(typeahead) - typeahead.setVal() // clear value on page reload - input.on('autocomplete:closed', clearSearch.bind(typeahead)) - input.on('autocomplete:cursorchanged autocomplete:cursorremoved', saveSearchState.bind(typeahead)) - input.on('autocomplete:selected', onSuggestionSelected.bind(typeahead)) - input.on('autocomplete:updated', onResultsUpdated.bind(typeahead)) - dropdown._ensureVisible = ensureVisible - menu.off('mousedown.aa') - menu.off('mouseenter.aa') - menu.off('mouseleave.aa') - var suggestionSelector = '.' + dropdown.cssClasses.prefix + dropdown.cssClasses.suggestion - menu.on('mousedown.aa', suggestionSelector, onSuggestionMouseDown.bind(dropdown)) - typeahead.$facetFilterInput = input - .closest('#' + searchField.id) - .find('.filter input') - .on('change', toggleFilter.bind(typeahead)) - .prop('checked', window.localStorage.getItem(SEARCH_FILTER_ACTIVE_KEY) === 'true') - menu.find('.ds-pagination--prev').on('click', paginate.bind(typeahead, -1)).css('visibility', 'hidden') - menu.find('.ds-pagination--next').on('click', paginate.bind(typeahead, 1)).css('visibility', 'hidden') - monitorCtrlKey.call(typeahead) - searchField.addEventListener('click', confineEvent) - document.documentElement.addEventListener('click', clearSearch.bind(typeahead)) - document.addEventListener('keydown', handleShortcuts.bind(typeahead)) - if (input.attr('autofocus') != null) input.focus() - window.addEventListener('pageshow', reactivateSearch.bind(typeahead)) - } - - function reactivateSearch (e) { - var navigation = window.performance.navigation || {} - if ('type' in navigation) { - if (navigation.type !== FORWARD_BACK_TYPE) { - return - } else if (e.persisted && !isClosed(this)) { - this.$input.focus() - this.$input.val(this.getVal()) - this.dropdown.datasets[0].page = this.dropdown.$menu.find('.ds-pagination--curr').data('page') - } else if (window.sessionStorage.getItem('docs:restore-search-on-back') === 'true') { - if (!window.matchMedia('(min-width: 1024px)').matches) document.querySelector('.navbar-burger').click() - restoreSearch.call(this) - } - } - window.sessionStorage.removeItem('docs:restore-search-on-back') - } - - function appendStylesheet (href) { - document.head.appendChild(Object.assign(document.createElement('link'), { rel: 'stylesheet', href: href })) - } - - function onResultsUpdated () { - var dropdown = this.dropdown - var restoring = dropdown.restoring - delete dropdown.restoring - if (isClosed(this)) return - updatePagination.call(dropdown) - if (restoring && restoring.query === this.getVal() && restoring.filter === this.$facetFilterInput.prop('checked')) { - var cursor = restoring.cursor - if (cursor) dropdown._moveCursor(cursor) - } else { - saveSearchState.call(this) - } - } - - function toggleFilter (e) { - if ('restoring' in this.dropdown) return - window.localStorage.setItem(SEARCH_FILTER_ACTIVE_KEY, e.target.checked) - isClosed(this) ? this.$input.focus() : requery.call(this) - } - - function confineEvent (e) { - e.stopPropagation() - } - - function ensureVisible (el) { - var container = getScrollableResultsContainer(this)[0] - if (container.scrollHeight === container.offsetHeight) return - var delta - var item = el[0] - if ((delta = 15 + item.offsetTop + item.offsetHeight - (container.offsetHeight + container.scrollTop)) > 0) { - container.scrollTop += delta - } - if ((delta = item.offsetTop - container.scrollTop) < 0) { - container.scrollTop += delta - } - } - - function getScrollableResultsContainer (dropdown) { - return dropdown.datasets[0].$el - } - - function handleShortcuts (e) { - var target = e.target || {} - if (e.ctrlKey && e.keyCode === LT_KEY_CODE && target === this.$input[0]) return restoreSearch.call(this) - if (e.altKey || e.shiftKey || target.isContentEditable || 'disabled' in target) return - if (e.ctrlKey ? e.keyCode === SOLIDUS_KEY_CODE : e.keyCode === S_KEY_CODE) { + }, + baseAlgoliaOptions: baseAlgoliaOptions, + }) + var input = controller.input + var typeahead = input.data('aaAutocomplete') + var dropdown = typeahead.dropdown + var menu = dropdown.$menu + var dataset = dropdown.datasets[0] + dataset.cache = false + dataset.maxResults = config.maxResults || 500 + dataset.pageSize = baseAlgoliaOptions.hitsPerPage + dataset.source = controller.getAutocompleteSource(undefined, processQuery.bind(typeahead, controller)) + delete dataset.templates.footer + controller.queryDataCallback = processQueryData.bind(typeahead) + typeahead.setVal() // clear value on page reload + input.on('autocomplete:closed', clearSearch.bind(typeahead)) + input.on('autocomplete:cursorchanged autocomplete:cursorremoved', saveSearchState.bind(typeahead)) + input.on('autocomplete:selected', onSuggestionSelected.bind(typeahead)) + input.on('autocomplete:updated', onResultsUpdated.bind(typeahead)) + dropdown._ensureVisible = ensureVisible + menu.off('mousedown.aa') + menu.off('mouseenter.aa') + menu.off('mouseleave.aa') + var suggestionSelector = '.' + dropdown.cssClasses.prefix + dropdown.cssClasses.suggestion + menu.on('mousedown.aa', suggestionSelector, onSuggestionMouseDown.bind(dropdown)) + menu.find('.ds-pagination--prev').on('click', paginate.bind(typeahead, -1)).addClass('inactive') + menu.find('.ds-pagination--next').on('click', paginate.bind(typeahead, 1)).addClass('inactive') + monitorCtrlKey.call(typeahead) + searchField.addEventListener('click', confineEvent) + document.documentElement.addEventListener('click', clearSearch.bind(typeahead)) + document.addEventListener('keydown', handleShortcuts.bind(typeahead)) + if (input.attr('autofocus') != null) input.focus() + window.addEventListener('pageshow', reactivateSearch.bind(typeahead)) + } + + function reactivateSearch (e) { + var navigation = window.performance.navigation || {} + if ('type' in navigation) { + if (navigation.type !== FORWARD_BACK_TYPE) { + return + } else if (e.persisted && !isClosed(this)) { this.$input.focus() - e.preventDefault() - e.stopPropagation() + this.$input.val(this.getVal()) + this.dropdown.datasets[0].page = this.dropdown.$menu.find('.ds-pagination--curr').data('page') + } else if (window.sessionStorage.getItem('docs:restore-search-on-back') === 'true') { + if (!window.matchMedia('(min-width: 1024px)').matches) document.querySelector('.navbar-burger').click() + restoreSearch.call(this) } } - - function isClosed (typeahead) { - var query = typeahead.getVal() - return !query || query !== typeahead.dropdown.datasets[0].query + window.sessionStorage.removeItem('docs:restore-search-on-back') + } + + function appendStylesheet (href) { + document.head.appendChild(Object.assign(document.createElement('link'), { rel: 'stylesheet', href: href })) + } + + function onResultsUpdated () { + var dropdown = this.dropdown + var restoring = dropdown.restoring + delete dropdown.restoring + if (isClosed(this)) return + updatePagination.call(dropdown) + if (restoring && restoring.query === this.getVal()) { + var cursor = restoring.cursor + if (cursor) dropdown._moveCursor(cursor) + } else { + saveSearchState.call(this) } - - function monitorCtrlKey () { - this.$input.on('keydown', onCtrlKeyDown.bind(this)) - this.dropdown.$menu.on('keyup', onCtrlKeyUp.bind(this)) + } + + function confineEvent (e) { + e.stopPropagation() + } + + function ensureVisible (el) { + var container = getScrollableResultsContainer(this)[0] + if (container.scrollHeight === container.offsetHeight) return + var delta + var item = el[0] + if ((delta = 15 + item.offsetTop + item.offsetHeight - (container.offsetHeight + container.scrollTop)) > 0) { + container.scrollTop += delta } - - function onCtrlKeyDown (e) { - if (e.keyCode !== CTRL_KEY_CODE) return - this.ctrlKeyDown = true - var container = getScrollableResultsContainer(this.dropdown) - var prevScrollTop = container.scrollTop() - this.dropdown.getCurrentCursor().find('a').focus() - container.scrollTop(prevScrollTop) // calling focus can cause the container to scroll, so restore it + if ((delta = item.offsetTop - container.scrollTop) < 0) { + container.scrollTop += delta } - - function onCtrlKeyUp (e) { - if (e.keyCode !== CTRL_KEY_CODE) return - delete this.ctrlKeyDown + } + + function getScrollableResultsContainer (dropdown) { + return dropdown.datasets[0].$el + } + + function handleShortcuts (e) { + var target = e.target || {} + if (e.ctrlKey && e.keyCode === LT_KEY_CODE && target === this.$input[0]) return restoreSearch.call(this) + if (e.altKey || e.shiftKey || target.isContentEditable || 'disabled' in target) return + if (e.ctrlKey ? e.keyCode === SOLIDUS_KEY_CODE : e.keyCode === S_KEY_CODE) { this.$input.focus() - } - - function onSuggestionMouseDown (e) { - var dropdown = this - var suggestion = dropdown._getSuggestions().filter('#' + e.currentTarget.id) - if (suggestion[0] === dropdown._getCursor()[0]) return - dropdown._removeCursor() - dropdown._setCursor(suggestion, false) - } - - function onSuggestionSelected (e, suggestion, datasetNum, context) { - if (!this.ctrlKeyDown) { - if (context.selectionMethod === 'click') saveSearchState.call(this) - window.sessionStorage.setItem('docs:restore-search-on-back', 'true') - } - e.isDefaultPrevented = function () { - return true - } - } - - function paginate (delta, e) { e.preventDefault() - var dataset = this.dropdown.datasets[0] - dataset.page = (dataset.page || 0) + delta - requery.call(this) + e.stopPropagation() } - - function updatePagination () { - var result = this.datasets[0].result - var page = result.page - var menu = this.$menu - menu - .find('.ds-pagination--curr') - .html(result.pages ? 'Page ' + (page + 1) + ' of ' + result.pages : 'No results') - .data('page', page) - menu.find('.ds-pagination--prev').css('visibility', page > 0 ? '' : 'hidden') - menu.find('.ds-pagination--next').css('visibility', result.pages > page + 1 ? '' : 'hidden') - getScrollableResultsContainer(this).scrollTop(0) + } + + function isClosed (typeahead) { + var query = typeahead.getVal() + return !query || query !== typeahead.dropdown.datasets[0].query + } + + function monitorCtrlKey () { + this.$input.on('keydown', onCtrlKeyDown.bind(this)) + this.dropdown.$menu.on('keyup', onCtrlKeyUp.bind(this)) + } + + function onCtrlKeyDown (e) { + if (e.keyCode !== CTRL_KEY_CODE) return + this.ctrlKeyDown = true + var container = getScrollableResultsContainer(this.dropdown) + var prevScrollTop = container.scrollTop() + this.dropdown.getCurrentCursor().find('a').focus() + container.scrollTop(prevScrollTop) // calling focus can cause the container to scroll, so restore it + } + + function onCtrlKeyUp (e) { + if (e.keyCode !== CTRL_KEY_CODE) return + delete this.ctrlKeyDown + this.$input.focus() + } + + function onSuggestionMouseDown (e) { + var dropdown = this + var suggestion = dropdown._getSuggestions().filter('#' + e.currentTarget.id) + if (suggestion[0] === dropdown._getCursor()[0]) return + dropdown._removeCursor() + dropdown._setCursor(suggestion, false) + } + + function onSuggestionSelected (e, suggestion, datasetNum, context) { + if (!this.ctrlKeyDown) { + if (context.selectionMethod === 'click') saveSearchState.call(this) + window.sessionStorage.setItem('docs:restore-search-on-back', 'true') } - - function requery (query) { - this.$input.focus() - query === undefined ? (query = this.input.getInputValue()) : this.input.setInputValue(query, true) - this.input.setQuery(query) - this.dropdown.update(query) - this.dropdown.open() - } - - function clearSearch () { - this.isActivated = true // we can't rely on this state being correct - this.setVal() - delete this.ctrlKeyDown - delete this.dropdown.datasets[0].result + e.isDefaultPrevented = function () { + return true } - - function processQuery (controller, query) { - var algoliaOptions = {} - if (this.$facetFilterInput.prop('checked')) { - algoliaOptions.facetFilters = [this.$facetFilterInput.data('facetFilter')] + } + + function updateFilters () { + var facetFilters = this.dropdown.$menu + .find('.ds-filter input') + .map(function () { + if (this.checked) { + var version = this.parentNode.querySelector('select') + return version && version.value ? version.value : this.value + } + }) + .get() + this.dropdown.datasets[0].filters = facetFilters.length ? facetFilters.join(' OR ') : '' + } + + function paginate (delta, e) { + e.preventDefault() + var dataset = this.dropdown.datasets[0] + dataset.page = (dataset.page || 0) + delta + requery.call(this) + } + + function updatePagination () { + var result = this.datasets[0].result + var page = result.page + var menu = this.$menu + menu + .find('.ds-pagination--curr') + .html(result.pages ? '[ Page ' + (page + 1) + ' of ' + result.pages + ' ]' : 'No results') + .data('page', page) + menu.find('.ds-pagination--prev').toggleClass('inactive', page < 1) + menu.find('.ds-pagination--next').toggleClass('inactive', page + 1 >= result.pages) + getScrollableResultsContainer(this).scrollTop(0) + } + + function requery (query) { + this.$input.focus() + query === undefined ? (query = this.input.getInputValue()) : this.input.setInputValue(query, true) + this.input.setQuery(query) + this.dropdown.update(query) + this.dropdown.open() + } + + function clearSearch () { + this.isActivated = true // we can't rely on this state being correct + this.setVal() + delete this.ctrlKeyDown + delete this.dropdown.datasets[0].result + } + + function processQuery (controller, query) { + var algoliaOptions = {} + var dropdown = this.dropdown + var dataset = dropdown.datasets[0] + if (!dropdown.restoring) { + var activeResult = dropdown.isEmpty ? undefined : dataset.result + if (activeResult && query === activeResult.query) { + if (dataset.filters !== dataset.result.filters) dataset.page = 0 + } else { + dataset.filters = '' + algoliaOptions.hitsPerPage = dataset.maxResults + dataset.page = 0 } - var dataset = this.dropdown.datasets[0] - var activeResult = dataset.result - algoliaOptions.page = !activeResult || query === activeResult.query ? dataset.page || 0 : (dataset.page = 0) - controller.algoliaOptions = Object.keys(algoliaOptions).length - ? Object.assign({}, controller.baseAlgoliaOptions, algoliaOptions) - : controller.baseAlgoliaOptions } - - function processQueryData (data) { - var result = data.results[0] - this.dropdown.datasets[0].result = { page: result.page, pages: result.nbPages, query: result.query } - result.hits = preserveHitOrder(result.hits) + if (dataset.filters) algoliaOptions.filters = dataset.filters + if (dataset.page) algoliaOptions.page = dataset.page + controller.algoliaOptions = Object.keys(algoliaOptions).length + ? Object.assign({}, controller.baseAlgoliaOptions, algoliaOptions) + : controller.baseAlgoliaOptions + } + + function processQueryData (data) { + var result = data.results[0] + var query = result.query + var filters = ~result.params.indexOf('filters=') + ? window.decodeURIComponent( + result.params + .split('&') + .find(function (param) { + return param.startsWith('filters=') + }) + .substr(8) + ) + : '' + var dataset = this.dropdown.datasets[0] + var activeResult = dataset.result + if (!activeResult || activeResult.query !== query) { + var components = this.dropdown.restoring + ? dataset.components + : (dataset.components = getFilterableComponents(result.hits)) + renderFilters.call(this, components, filters) } - - // preserves the original order of results by qualifying unique occurrences of the same lvl0 and lvl1 values - function preserveHitOrder (hits) { - var prevLvl0 - var lvl0Qualifiers = {} - var lvl1Qualifiers = {} - return hits.map(function (hit) { - var lvl0 = hit.hierarchy.lvl0 - var lvl1 = hit.hierarchy.lvl1 - var lvl0Qualifier = lvl0Qualifiers[lvl0] - if (lvl0 !== prevLvl0) { - lvl0Qualifiers[lvl0] = lvl0Qualifier == null ? (lvl0Qualifier = '') : (lvl0Qualifier += ' ') - lvl1Qualifiers = {} - } - if (lvl0Qualifier) hit.hierarchy.lvl0 = lvl0 + lvl0Qualifier - if (lvl1 in lvl1Qualifiers) { - hit.hierarchy.lvl1 = lvl1 + (lvl1Qualifiers[lvl1] += ' ') - } else { - lvl1Qualifiers[lvl1] = '' + var pages = Math.ceil(Math.min(result.nbHits, dataset.maxResults) / dataset.pageSize) + dataset.result = { filters: filters, page: result.page, pages: pages, query: result.query } + result.hits = preserveHitOrder(result.hits.slice(0, dataset.pageSize)) + } + + function getFilterableComponents (hits) { + return Object.entries( + hits.reduce(function (accum, hit) { + var componentVersionInfo = extractComponentVersionInfo(hit) + var name = componentVersionInfo.name + var version = componentVersionInfo.version + var component = accum[name] || (accum[name] = { title: componentVersionInfo.title }) + if (version && hit.component_version) { + var versions = component.versions || (component.versions = {}) + if (!(version in versions)) { + if (Array.isArray(hit.component_version) && hit.component_version[1] === name) { + component.latestVersion = version + } + versions[version] = hit.display_version || version + } } - prevLvl0 = lvl0 - return hit + return accum + }, {}) + ) + .sort(function (a, b) { + return a[1].title.replace(/^\./, '').localeCompare(b[1].title.replace(/^\./, '')) }) + .reduce(function (componentAccum, componentEntry) { + var component = componentEntry[1] + var versions = component.versions + if (versions && (versions = Object.entries(versions)).length > 1) { + component.versions = versions + .sort(function (a, b) { + return -a[0].localeCompare(b[0], undefined, { numeric: true, sensitivity: 'base' }) + }) + .reduce(function (versionAccum, versionEntry) { + versionAccum[versionEntry[0]] = versionEntry[1] + return versionAccum + }, {}) + } + componentAccum[componentEntry[0]] = component + return componentAccum + }, {}) + } + + function extractComponentVersionInfo (hit) { + var name, title, version + var componentVersion = hit.component_version + if (componentVersion) { + componentVersion = (Array.isArray(componentVersion) ? componentVersion[0] : componentVersion).split('@') + name = componentVersion[0] + version = componentVersion[1] + title = hit.component_title + } else { + name = hit.component + componentVersion = (hit.hierarchy.lvl0 || name).split(/ (?=\d+(?:\.|$))/) + title = componentVersion[0] + version = componentVersion[1] } - - function readSavedSearchState () { - try { - var state = window.localStorage.getItem(SAVED_SEARCH_STATE_KEY) - if (state && (state = JSON.parse(state))._version.toString() === SAVED_SEARCH_STATE_VERSION) return state - } catch (e) { - window.localStorage.removeItem(SAVED_SEARCH_STATE_KEY) - } - } - - function restoreSearch () { - var searchState = readSavedSearchState() - if (!searchState) return - this.dropdown.restoring = searchState - this.$facetFilterInput.prop('checked', searchState.filter) // change event will be ignored - var dataset = this.dropdown.datasets[0] - dataset.page = searchState.page - delete dataset.result - requery.call(this, searchState.query) // cursor is restored by onResultsUpdated => - } - - function saveSearchState () { - if (isClosed(this)) return - window.localStorage.setItem( - SAVED_SEARCH_STATE_KEY, - JSON.stringify({ - _version: SAVED_SEARCH_STATE_VERSION, - cursor: this.dropdown.getCurrentCursor().index() + 1, - filter: this.$facetFilterInput.prop('checked'), - page: this.dropdown.datasets[0].page, - query: this.getVal(), + return { name: name, version: version, title: title } + } + + function renderFilters (components, filters) { + var filterContainer = this.dropdown.$menu.find('.ds-filter').empty() + var selected = filters + ? filters.split(' OR ').reduce(function (accum, facetFilter) { + if (facetFilter.startsWith('component_version')) { + var valueParts = facetFilter.split(':')[1].split('@') + accum.push('component:' + valueParts[0]) + } + accum.push(facetFilter) + return accum + }, []) + : undefined + var typeahead = this + Object.entries(components).forEach(function (componentEntry) { + var componentName = componentEntry[0] + var component = componentEntry[1] + var filterOption = document.createElement('div') + var checkbox = Object.assign(document.createElement('input'), { + id: 'facet-filter-' + componentName, + type: 'checkbox', + value: 'component:' + componentName, + }) + if (selected && ~selected.indexOf(checkbox.value)) checkbox.checked = true + filterOption.appendChild(checkbox) + var label = Object.assign(document.createElement('label'), { htmlFor: 'facet-filter-' + componentName }) + label.appendChild(document.createTextNode(component.title)) + filterOption.appendChild(label) + var componentVersions = component.versions + var versions + if (componentVersions) { + versions = document.createElement('select') + if (Object.keys(componentVersions).length > 1) { + var wildcardVersionOption = Object.assign(document.createElement('option'), { value: '' }) + wildcardVersionOption.appendChild(document.createTextNode('All (*)')) + versions.appendChild(wildcardVersionOption) + } + Object.entries(componentVersions).forEach(function (componentVersionEntry) { + var version = componentVersionEntry[0] + var displayVersion = componentVersionEntry[1] + var versionOption = Object.assign(document.createElement('option'), { + value: 'component_version:' + componentName + '@' + version, + }) + if (checkbox.checked ? ~selected.indexOf(versionOption.value) : version === component.latestVersion) { + versionOption.selected = true + } + versionOption.appendChild(document.createTextNode(displayVersion)) + versions.appendChild(versionOption) }) - ) + filterOption.appendChild(versions) + } + filterContainer.append(filterOption) + checkbox.addEventListener('change', applyFilters.bind(typeahead)) + if (versions) versions.addEventListener('change', applyFilters.bind(typeahead)) + }) + } + + function applyFilters () { + updateFilters.call(this) + requery.call(this) + } + + // preserves original order of results by distinguishing non-sequential occurrences of the same lvl0 and lvl1 values + function preserveHitOrder (hits) { + var prevLvl0 + var lvl0Qualifiers = {} + var lvl1Qualifiers = {} + return hits.map(function (hit) { + var lvl0 = hit.hierarchy.lvl0 + var lvl1 = hit.hierarchy.lvl1 + if (!lvl0) { + if ((lvl0 = hit.component_title)) { + var displayVersion = hit.display_version || [].concat(hit.component_version)[0].split('@')[1] + lvl0 = hit.hierarchy.lvl0 = lvl0 + (displayVersion ? ' ' + displayVersion : '') + } else { + lvl0 = hit.hierarchy.lvl0 = hit.component + (hit.version ? ' ' + hit.version : '') + } + } + if (!lvl1) lvl1 = hit.hierarchy.lvl1 = lvl0 + var lvl0Qualifier = lvl0Qualifiers[lvl0] + if (lvl0 !== prevLvl0) { + lvl0Qualifiers[lvl0] = lvl0Qualifier == null ? (lvl0Qualifier = '') : (lvl0Qualifier += ' ') + lvl1Qualifiers = {} + } + if (lvl0Qualifier) hit.hierarchy.lvl0 = lvl0 + lvl0Qualifier + if (lvl1 in lvl1Qualifiers) { + hit.hierarchy.lvl1 = lvl1 + (lvl1Qualifiers[lvl1] += ' ') + } else { + lvl1Qualifiers[lvl1] = '' + } + prevLvl0 = lvl0 + return hit + }) + } + + function readSavedSearchState () { + try { + var state = window.localStorage.getItem(SAVED_SEARCH_STATE_KEY) + if (state && (state = JSON.parse(state))._version.toString() === SAVED_SEARCH_STATE_VERSION) return state + } catch (e) { + window.localStorage.removeItem(SAVED_SEARCH_STATE_KEY) } - })() \ No newline at end of file + } + + function restoreSearch () { + var searchState = readSavedSearchState() + if (!searchState) return + this.dropdown.restoring = searchState + var dataset = this.dropdown.datasets[0] + dataset.components = searchState.components + dataset.filters = searchState.filters + dataset.page = searchState.page + delete dataset.result + requery.call(this, searchState.query) // cursor is restored by onResultsUpdated => + } + + function saveSearchState () { + if (isClosed(this)) return + window.localStorage.setItem( + SAVED_SEARCH_STATE_KEY, + JSON.stringify({ + _version: SAVED_SEARCH_STATE_VERSION, + components: this.dropdown.datasets[0].components, + cursor: this.dropdown.getCurrentCursor().index() + 1, + filters: this.dropdown.datasets[0].filters, + page: this.dropdown.datasets[0].page, + query: this.getVal(), + }) + ) + } +})() \ No newline at end of file