Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Robert Kaussow <mail@thegeeklab.de>
236 lines
6 KiB
JavaScript
236 lines
6 KiB
JavaScript
import groupBy from "lodash/groupBy.js"
|
||
import truncate from "lodash/truncate.js"
|
||
import Document from "flexsearch/dist/module/document.js"
|
||
import { Validator } from "@cfworker/json-schema"
|
||
|
||
document.addEventListener("DOMContentLoaded", function () {
|
||
const input = document.querySelector("#gdoc-search-input")
|
||
const results = document.querySelector("#gdoc-search-results")
|
||
const basePath = urlPath(input ? input.dataset.siteBaseUrl : "")
|
||
const lang = input ? input.dataset.siteLang : ""
|
||
|
||
const configSchema = {
|
||
type: "object",
|
||
properties: {
|
||
dataFile: {
|
||
type: "string"
|
||
},
|
||
indexConfig: {
|
||
type: ["object", "null"]
|
||
},
|
||
showParent: {
|
||
type: "boolean"
|
||
},
|
||
showDescription: {
|
||
type: "boolean"
|
||
}
|
||
},
|
||
additionalProperties: false
|
||
}
|
||
const validator = new Validator(configSchema)
|
||
|
||
if (!input) return
|
||
|
||
getJson(combineURLs(basePath, "/search/" + lang + ".config.min.json"), function (searchConfig) {
|
||
const validationResult = validator.validate(searchConfig)
|
||
|
||
if (!validationResult.valid)
|
||
throw AggregateError(
|
||
validationResult.errors.map((err) => new Error("Validation error: " + err.error)),
|
||
"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", "description"],
|
||
store: ["title", "href", "parent", "description"]
|
||
}
|
||
|
||
const index = new 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: 5
|
||
}
|
||
|
||
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"))
|
||
|
||
if (!section) {
|
||
title.remove()
|
||
}
|
||
title.classList.add("gdoc-search__section")
|
||
title.textContent = section
|
||
createLinks(searchHits[section], subList, searchConfig.showDescription)
|
||
|
||
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, searchConfig.showDescription)
|
||
|
||
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, showDesc) {
|
||
const items = []
|
||
|
||
for (const page of pages) {
|
||
const item = document.createElement("li"),
|
||
a = item.appendChild(document.createElement("a")),
|
||
entry = a.appendChild(document.createElement("span"))
|
||
|
||
a.href = page.href
|
||
entry.classList.add("gdoc-search__entry--title")
|
||
entry.textContent = page.title
|
||
a.classList.add("gdoc-search__entry")
|
||
|
||
if (showDesc === true) {
|
||
const desc = a.appendChild(document.createElement("span"))
|
||
desc.classList.add("gdoc-search__entry--description")
|
||
desc.textContent = truncate(page.description, {
|
||
length: 55,
|
||
separator: " "
|
||
})
|
||
}
|
||
|
||
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
|
||
}
|
||
|
||
function urlPath(rawURL) {
|
||
var parser = document.createElement("a")
|
||
parser.href = rawURL
|
||
|
||
return parser.pathname
|
||
}
|
||
|
||
/**
|
||
* Part of [axios](https://github.com/axios/axios/blob/master/lib/helpers/combineURLs.js).
|
||
* Creates a new URL by combining the specified URLs
|
||
*
|
||
* @param {string} baseURL The base URL
|
||
* @param {string} relativeURL The relative URL
|
||
* @returns {string} The combined URL
|
||
*/
|
||
function combineURLs(baseURL, relativeURL) {
|
||
return relativeURL
|
||
? baseURL.replace(/\/+$/, "") + "/" + relativeURL.replace(/^\/+/, "")
|
||
: baseURL
|
||
}
|