const constants = { noResult: { l: "No results found", renderable: "No results found" }, labels: { modules: 'Modules', packages: 'Packages', types: 'Types', members: 'Members', tags: 'SearchTags' } } //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; } 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 } $.widget("custom.catcomplete", $.ui.autocomplete, { _create: function() { this._super(); }, _renderMenu: function(ul, items) { const menu = this; let category $.each(items, (index, item) => { const shouldCategoryLabelBeRendered = itemHasResults(item) && item.category !== category if (shouldCategoryLabelBeRendered) { ul.append(`
  • ${item.category}
  • `); category = item.category; } const li = menu._renderItemData(ul, item); if (item.category) { li.attr("aria-label", `${item.category} : ${item.l}`); } else { li.attr("aria-label", item.l); } li.attr("class", "resultItem"); }); }, _renderItem: (ul, item) => { const li = $("
  • ").appendTo(ul); const div = $("
    ").appendTo(li); div.html(item.renderable); return li; } }); const highlight = (match) => `` + match + `` 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) $(() => { $("#search").catcomplete({ minLength: 1, delay: 100, source: function(request, response) { 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"); const processWithExactLabel = (dataset, category) => { const exactOrCamelMatches = [] const secondaryMatches = [] 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) } }) return [...exactOrCamelMatches, ...secondaryMatches] } 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) } }) return [...exactOrCamelMatches, ...secondaryMatches] } 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) } }) return [...exactOrCamelMatches, ...secondaryMatches] } 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); }, response: function(event, ui) { if (!ui.content.length) { ui.content.push(constants.noResult); } else { $("#search").empty(); } }, autoFocus: true, position: { collision: "flip" }, select: function(event, ui) { if (ui.item.l !== constants.noResult.l) { window.location.href = pathtoroot + ui.item.url; $("#search").focus(); } } }); });