const { groupBy } = require("./groupBy") const FlexSearch = require("flexsearch") const Ajv = require("ajv") document.addEventListener("DOMContentLoaded", function (event) { const ajv = new Ajv() const input = document.querySelector("#gdoc-search-input") const results = document.querySelector("#gdoc-search-results") const configSchema = { type: "object", properties: { dataFile: { type: "string" }, indexConfig: { type: ["object", "null"] }, showParent: { type: "boolean" } }, additionalProperties: false } getJson("/search/config.min.json", function (searchConfig) { const configValidate = ajv.compile(configSchema) const valid = configValidate(searchConfig) if (!valid) throw AggregateError( configValidate.errors.map( (err) => new Error(["Validation error:", err.instancePath, err.keyword, err.message].join(" ")) ), "Schema validation failed" ) if (input) { input.addEventListener("focus", () => { init(input, searchConfig) }) input.addEventListener("keyup", () => { search(input, results, searchConfig) }) } }) }) function init(input, searchConfig) { input.removeEventListener("focus", init) const indexCfgDefaults = { tokenize: "forward" } const indexCfg = searchConfig.indexConfig ? searchConfig.indexConfig : indexCfgDefaults const dataUrl = searchConfig.dataFile indexCfg.document = { key: "id", index: ["title", "content"], store: ["title", "href", "parent"] } const index = new FlexSearch.Document(indexCfg) window.geekdocSearchIndex = index getJson(dataUrl, function (data) { data.forEach((obj) => { window.geekdocSearchIndex.add(obj) }) }) } function search(input, results, searchConfig) { const searchCfg = { enrich: true, limit: 10 } while (results.firstChild) { results.removeChild(results.firstChild) } if (!input.value) { return results.classList.remove("has-hits") } let searchHits = flattenHits(window.geekdocSearchIndex.search(input.value, searchCfg)) if (searchHits.length < 1) { return results.classList.remove("has-hits") } results.classList.add("has-hits") if (searchConfig.showParent === true) { searchHits = groupBy(searchHits, (hit) => hit.parent) } const items = [] if (searchConfig.showParent === true) { for (const section in searchHits) { const item = document.createElement("li"), title = item.appendChild(document.createElement("span")), subList = item.appendChild(document.createElement("ul")) title.textContent = section createLinks(searchHits[section], subList) items.push(item) } } else { const item = document.createElement("li"), title = item.appendChild(document.createElement("span")), subList = item.appendChild(document.createElement("ul")) title.textContent = "Results" createLinks(searchHits, subList) items.push(item) } items.forEach((item) => { results.appendChild(item) }) } /** * Creates links to given fields and either returns them in an array or attaches them to a target element * @param {Object} fields Page to which the link should point to * @param {HTMLElement} target Element to which the links should be attatched * @returns {Array} If target is not specified, returns an array of built links */ function createLinks(pages, target) { const items = [] for (const page of pages) { const item = document.createElement("li"), entry = item.appendChild(document.createElement("span")), a = entry.appendChild(document.createElement("a")) entry.classList.add("flex") a.href = page.href a.textContent = page.title a.classList.add("gdoc-search__entry") if (target) { target.appendChild(item) continue } items.push(item) } return items } function fetchErrors(response) { if (!response.ok) { throw Error("Failed to fetch '" + response.url + "': " + response.statusText) } return response } function getJson(src, callback) { fetch(src) .then(fetchErrors) .then((response) => response.json()) .then((json) => callback(json)) .catch(function (error) { if (error instanceof AggregateError) { console.error(error.message) error.errors.forEach((element) => { console.error(element) }) } else { console.error(error) } }) } function flattenHits(results) { const items = [] const map = new Map() for (const field of results) { for (const page of field.result) { if (!map.has(page.doc.href)) { map.set(page.doc.href, true) items.push(page.doc) } } } return items }