2023-08-22 23:00:49 +03:00
|
|
|
const constants = {
|
|
|
|
noResult: {
|
|
|
|
l: "No results found",
|
|
|
|
renderable: "No results found"
|
|
|
|
},
|
|
|
|
labels: {
|
|
|
|
modules: 'Modules',
|
|
|
|
packages: 'Packages',
|
|
|
|
types: 'Types',
|
|
|
|
members: 'Members',
|
|
|
|
tags: 'SearchTags'
|
2022-09-17 20:29:42 +03:00
|
|
|
}
|
|
|
|
}
|
2023-08-22 23:00:49 +03:00
|
|
|
|
|
|
|
//It is super important to have vars here since they are lifter outside the block
|
|
|
|
//ES6 syntax doesn't provide those feature and therefore will fail when one of those values wouldn't be initialized
|
|
|
|
//eg. when a request for a given package fails
|
|
|
|
if(typeof moduleSearchIndex === 'undefined'){
|
|
|
|
var moduleSearchIndex;
|
2022-09-17 20:29:42 +03:00
|
|
|
}
|
2023-08-22 23:00:49 +03:00
|
|
|
if(typeof packageSearchIndex === 'undefined'){
|
|
|
|
var packageSearchIndex;
|
|
|
|
}
|
|
|
|
if(typeof typeSearchIndex === 'undefined'){
|
|
|
|
var typeSearchIndex;
|
|
|
|
}
|
|
|
|
if(typeof memberSearchIndex === 'undefined'){
|
|
|
|
var memberSearchIndex;
|
|
|
|
}
|
|
|
|
if(typeof tagSearchIndex === 'undefined'){
|
|
|
|
var tagSearchIndex;
|
|
|
|
}
|
|
|
|
|
|
|
|
const clearElementValue = (element) => {
|
|
|
|
element.val('')
|
|
|
|
}
|
|
|
|
|
|
|
|
$(function init() {
|
|
|
|
const search = $("#search")
|
|
|
|
const reset = $("#reset")
|
|
|
|
|
|
|
|
clearElementValue(search)
|
|
|
|
reset.on('click', () => {
|
|
|
|
clearElementValue(search)
|
|
|
|
search.focus()
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
const itemHasResults = (item) => {
|
|
|
|
return item.l !== constants.noResult
|
|
|
|
}
|
|
|
|
|
2022-09-17 20:29:42 +03:00
|
|
|
$.widget("custom.catcomplete", $.ui.autocomplete, {
|
|
|
|
_create: function() {
|
|
|
|
this._super();
|
|
|
|
},
|
|
|
|
_renderMenu: function(ul, items) {
|
2023-08-22 23:00:49 +03:00
|
|
|
const menu = this;
|
|
|
|
let category
|
|
|
|
$.each(items, (index, item) => {
|
|
|
|
const shouldCategoryLabelBeRendered = itemHasResults(item) && item.category !== category
|
|
|
|
if (shouldCategoryLabelBeRendered) {
|
|
|
|
ul.append(`<li class="ui-autocomplete-category">${item.category}</li>`);
|
|
|
|
category = item.category;
|
2022-09-17 20:29:42 +03:00
|
|
|
}
|
2023-08-22 23:00:49 +03:00
|
|
|
|
|
|
|
const li = menu._renderItemData(ul, item);
|
2022-09-17 20:29:42 +03:00
|
|
|
if (item.category) {
|
2023-08-22 23:00:49 +03:00
|
|
|
li.attr("aria-label", `${item.category} : ${item.l}`);
|
2022-09-17 20:29:42 +03:00
|
|
|
} else {
|
|
|
|
li.attr("aria-label", item.l);
|
|
|
|
}
|
2023-08-22 23:00:49 +03:00
|
|
|
li.attr("class", "resultItem");
|
2022-09-17 20:29:42 +03:00
|
|
|
});
|
|
|
|
},
|
2023-08-22 23:00:49 +03:00
|
|
|
_renderItem: (ul, item) => {
|
|
|
|
const li = $("<li/>").appendTo(ul);
|
|
|
|
const div = $("<div/>").appendTo(li);
|
|
|
|
div.html(item.renderable);
|
2022-09-17 20:29:42 +03:00
|
|
|
return li;
|
|
|
|
}
|
|
|
|
});
|
2023-08-22 23:00:49 +03:00
|
|
|
|
|
|
|
const highlight = (match) => `<span class="resultHighlight">` + match + `</span>`
|
|
|
|
const escapeHtml = (str) => str.replace("&", "&").replace("<", "<").replace(">", ">")
|
|
|
|
|
|
|
|
const labelForPackage = (element) => (element.m) ? (element.m + "/" + element.l) : element.l
|
|
|
|
const labelForNested = (element) => {
|
|
|
|
var label = ""
|
|
|
|
if(element.p) label += `${element.p}.`
|
|
|
|
if(element.l !== element.c && element.c) label += `${element.c}.`
|
|
|
|
return label + element.l
|
|
|
|
}
|
|
|
|
const nestedName = (e) => e.l.substring(e.l.lastIndexOf(".") + 1)
|
|
|
|
|
|
|
|
const renderableFromLabel = (label, regex) => escapeHtml(label).replace(regex, highlight)
|
|
|
|
|
|
|
|
$(() => {
|
2022-09-17 20:29:42 +03:00
|
|
|
$("#search").catcomplete({
|
|
|
|
minLength: 1,
|
|
|
|
delay: 100,
|
|
|
|
source: function(request, response) {
|
2023-08-22 23:00:49 +03:00
|
|
|
const exactRegexp = $.ui.autocomplete.escapeRegex(request.term) + "$"
|
|
|
|
const exactMatcher = new RegExp("^" + exactRegexp, "i");
|
|
|
|
const camelCaseRegexp = ($.ui.autocomplete.escapeRegex(request.term)).split(/(?=[A-Z])/).join("([a-z0-9_$]*?)");
|
|
|
|
const camelCaseMatcher = new RegExp("^" + camelCaseRegexp);
|
|
|
|
const secondaryMatcher = new RegExp($.ui.autocomplete.escapeRegex(request.term), "i");
|
2022-09-17 20:29:42 +03:00
|
|
|
|
2023-08-22 23:00:49 +03:00
|
|
|
const processWithExactLabel = (dataset, category) => {
|
|
|
|
const exactOrCamelMatches = []
|
|
|
|
const secondaryMatches = []
|
2022-09-17 20:29:42 +03:00
|
|
|
|
2023-08-22 23:00:49 +03:00
|
|
|
dataset.map(element => {
|
|
|
|
element.category = category
|
|
|
|
return element
|
|
|
|
}).forEach((element) => {
|
|
|
|
if(exactMatcher.test(element.l)){
|
|
|
|
element.renderable = renderableFromLabel(element.l, exactMatcher)
|
|
|
|
exactOrCamelMatches.push(element)
|
|
|
|
} else if(camelCaseMatcher.test(element.l)){
|
|
|
|
element.renderable = renderableFromLabel(element.l, camelCaseMatcher)
|
|
|
|
exactOrCamelMatches.push(element)
|
|
|
|
} else if(secondaryMatcher.test(element.l)){
|
|
|
|
element.renderable = renderableFromLabel(element.l, secondaryMatcher)
|
|
|
|
secondaryMatches.push(element)
|
2022-09-17 20:29:42 +03:00
|
|
|
}
|
2023-08-22 23:00:49 +03:00
|
|
|
})
|
|
|
|
|
|
|
|
return [...exactOrCamelMatches, ...secondaryMatches]
|
2022-09-17 20:29:42 +03:00
|
|
|
}
|
2023-08-22 23:00:49 +03:00
|
|
|
|
|
|
|
const processPackages = (dataset) => {
|
|
|
|
const exactOrCamelMatches = []
|
|
|
|
const secondaryMatches = []
|
|
|
|
|
|
|
|
dataset.map(element => {
|
|
|
|
element.category = constants.labels.packages
|
|
|
|
return element
|
|
|
|
}).forEach((element) => {
|
|
|
|
const label = labelForPackage(element);
|
|
|
|
if(exactMatcher.test(element.l)){
|
|
|
|
element.renderable = renderableFromLabel(element.l, exactMatcher)
|
|
|
|
exactOrCamelMatches.push(element)
|
|
|
|
} else if(camelCaseMatcher.test(label)){
|
|
|
|
element.renderable = renderableFromLabel(label, camelCaseMatcher)
|
|
|
|
exactOrCamelMatches.push(element)
|
|
|
|
} else if(secondaryMatcher.test(label)){
|
|
|
|
element.renderable = renderableFromLabel(label, secondaryMatcher)
|
|
|
|
secondaryMatches.push(element)
|
2022-09-17 20:29:42 +03:00
|
|
|
}
|
2023-08-22 23:00:49 +03:00
|
|
|
})
|
|
|
|
|
|
|
|
return [...exactOrCamelMatches, ...secondaryMatches]
|
2022-09-17 20:29:42 +03:00
|
|
|
}
|
2023-08-22 23:00:49 +03:00
|
|
|
|
|
|
|
const processNested = (dataset, label) => {
|
|
|
|
const exactOrCamelMatches = []
|
|
|
|
const secondaryMatches = []
|
|
|
|
|
|
|
|
dataset.map(element => {
|
|
|
|
element.category = label
|
|
|
|
return element
|
|
|
|
}).forEach((element) => {
|
|
|
|
const label = nestedName(element);
|
|
|
|
if(exactMatcher.test(label)) {
|
|
|
|
element.renderable = renderableFromLabel(labelForNested(element), new RegExp(exactRegexp, "i"))
|
|
|
|
exactOrCamelMatches.push(element)
|
|
|
|
} else if(camelCaseMatcher.test(label)){
|
|
|
|
element.renderable = renderableFromLabel(labelForNested(element), new RegExp(camelCaseRegexp))
|
|
|
|
exactOrCamelMatches.push(element)
|
|
|
|
} else if(secondaryMatcher.test(labelForNested(element))){
|
|
|
|
element.renderable = renderableFromLabel(labelForNested(element), secondaryMatcher)
|
|
|
|
secondaryMatches.push(element)
|
2022-09-17 20:29:42 +03:00
|
|
|
}
|
2023-08-22 23:00:49 +03:00
|
|
|
})
|
|
|
|
|
|
|
|
return [...exactOrCamelMatches, ...secondaryMatches]
|
2022-09-17 20:29:42 +03:00
|
|
|
}
|
2023-08-22 23:00:49 +03:00
|
|
|
|
|
|
|
const modules = moduleSearchIndex ? processWithExactLabel(moduleSearchIndex, constants.labels.modules) : []
|
|
|
|
const packages = packageSearchIndex ? processPackages(packageSearchIndex) : []
|
|
|
|
const types = typeSearchIndex ? processNested(typeSearchIndex, constants.labels.types) : []
|
|
|
|
const members = memberSearchIndex ? processNested(memberSearchIndex, constants.labels.members) : []
|
|
|
|
const tags = tagSearchIndex ? processWithExactLabel(tagSearchIndex, constants.labels.tags) : []
|
|
|
|
|
|
|
|
const result = [...modules, ...packages, ...types, ...members, ...tags]
|
|
|
|
return response(result);
|
2022-09-17 20:29:42 +03:00
|
|
|
},
|
|
|
|
response: function(event, ui) {
|
|
|
|
if (!ui.content.length) {
|
2023-08-22 23:00:49 +03:00
|
|
|
ui.content.push(constants.noResult);
|
2022-09-17 20:29:42 +03:00
|
|
|
} else {
|
|
|
|
$("#search").empty();
|
|
|
|
}
|
|
|
|
},
|
|
|
|
autoFocus: true,
|
|
|
|
position: {
|
|
|
|
collision: "flip"
|
|
|
|
},
|
|
|
|
select: function(event, ui) {
|
2023-08-22 23:00:49 +03:00
|
|
|
if (ui.item.l !== constants.noResult.l) {
|
|
|
|
window.location.href = pathtoroot + ui.item.url;
|
2022-09-17 20:29:42 +03:00
|
|
|
$("#search").focus();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
2023-08-22 23:00:49 +03:00
|
|
|
|