diff --git a/app/frontend/controllers/accessible_autocomplete_controller.js b/app/frontend/controllers/accessible_autocomplete_controller.js index 935594529..b0ee52441 100644 --- a/app/frontend/controllers/accessible_autocomplete_controller.js +++ b/app/frontend/controllers/accessible_autocomplete_controller.js @@ -1,24 +1,24 @@ -import { Controller } from "@hotwired/stimulus"; -import accessibleAutocomplete from "accessible-autocomplete"; -import "accessible-autocomplete/dist/accessible-autocomplete.min.css"; -import { enhanceOption, suggestion, sort } from "../modules/search"; +import { Controller } from '@hotwired/stimulus' +import accessibleAutocomplete from 'accessible-autocomplete' +import 'accessible-autocomplete/dist/accessible-autocomplete.min.css' +import { enhanceOption, suggestion, sort } from '../modules/search' export default class extends Controller { - connect() { - const selectEl = this.element; - const selectOptions = Array.from(selectEl.options); - const options = selectOptions.map((o) => enhanceOption(o)); + connect () { + const selectEl = this.element + const selectOptions = Array.from(selectEl.options) + const options = selectOptions.map((o) => enhanceOption(o)) - const matches = /^(\w+)\[(\w+)\]$/.exec(selectEl.name); - const rawFieldName = `${matches[1]}[${matches[2]}_raw]`; + const matches = /^(\w+)\[(\w+)\]$/.exec(selectEl.name) + const rawFieldName = `${matches[1]}[${matches[2]}_raw]` accessibleAutocomplete.enhanceSelectElement({ - defaultValue: "", + defaultValue: '', selectElement: selectEl, minLength: 2, source: (query, populateResults) => { if (/\S/.test(query)) { - populateResults(sort(query, options)); + populateResults(sort(query, options)) } }, autoselect: true, @@ -28,9 +28,9 @@ export default class extends Controller { const selectedOption = [].filter.call( selectOptions, (option) => (option.textContent || option.innerText) === val - )[0]; - if (selectedOption) selectedOption.selected = true; - }, - }); + )[0] + if (selectedOption) selectedOption.selected = true + } + }) } } diff --git a/app/frontend/modules/search.js b/app/frontend/modules/search.js index 91966be9a..d9d233ab1 100644 --- a/app/frontend/modules/search.js +++ b/app/frontend/modules/search.js @@ -1,106 +1,106 @@ const addWeightWithBoost = (option, query) => { - option.weight = calculateWeight(option.clean, query) * option.clean.boost; + option.weight = calculateWeight(option.clean, query) * option.clean.boost - return option; -}; + return option +} const clean = (text) => text .trim() - .replace(/['’]/g, "") - .replace(/[.,"/#!$%^&*;:{}=\-_`~()]/g, " ") - .toLowerCase(); + .replace(/['’]/g, '') + .replace(/[.,"/#!$%^&*;:{}=\-_`~()]/g, ' ') + .toLowerCase() const cleanseOption = (option) => { option.clean = { name: clean(option.name), nameWithoutStopWords: removeStopWords(option.name), - boost: option.boost || 1, - }; + boost: option.boost || 1 + } - return option; -}; + return option +} -const hasWeight = (option) => option.weight > 0; +const hasWeight = (option) => option.weight > 0 const byWeightThenAlphabetically = (a, b) => { - if (a.weight > b.weight) return -1; - if (a.weight < b.weight) return 1; - if (a.name < b.name) return -1; - if (a.name > b.name) return 1; + if (a.weight > b.weight) return -1 + if (a.weight < b.weight) return 1 + if (a.name < b.name) return -1 + if (a.name > b.name) return 1 - return 0; -}; + return 0 +} -const optionName = (option) => option.name; -const exactMatch = (word, query) => word === query; +const optionName = (option) => option.name +const exactMatch = (word, query) => word === query -const startsWithRegExp = (query) => new RegExp("\\b" + query, "i"); -const startsWith = (word, query) => word.search(startsWithRegExp(query)) === 0; +const startsWithRegExp = (query) => new RegExp('\\b' + query, 'i') +const startsWith = (word, query) => word.search(startsWithRegExp(query)) === 0 const wordsStartsWithQuery = (word, regExps) => - regExps.every((regExp) => word.search(regExp) >= 0); + regExps.every((regExp) => word.search(regExp) >= 0) const calculateWeight = ({ name, nameWithoutStopWords }, query) => { - const queryWithoutStopWords = removeStopWords(query); + const queryWithoutStopWords = removeStopWords(query) - if (exactMatch(name, query)) return 100; - if (exactMatch(nameWithoutStopWords, queryWithoutStopWords)) return 95; + if (exactMatch(name, query)) return 100 + if (exactMatch(nameWithoutStopWords, queryWithoutStopWords)) return 95 - if (startsWith(name, query)) return 60; - if (startsWith(nameWithoutStopWords, queryWithoutStopWords)) return 55; + if (startsWith(name, query)) return 60 + if (startsWith(nameWithoutStopWords, queryWithoutStopWords)) return 55 const startsWithRegExps = queryWithoutStopWords .split(/\s+/) - .map(startsWithRegExp); + .map(startsWithRegExp) - if (wordsStartsWithQuery(nameWithoutStopWords, startsWithRegExps)) return 25; + if (wordsStartsWithQuery(nameWithoutStopWords, startsWithRegExps)) return 25 - return 0; -}; + return 0 +} -const stopWords = ["the", "of", "in", "and", "at", "&"]; +const stopWords = ['the', 'of', 'in', 'and', 'at', '&'] const removeStopWords = (text) => { const isAllStopWords = text .trim() - .split(" ") - .every((word) => stopWords.includes(word)); + .split(' ') + .every((word) => stopWords.includes(word)) if (isAllStopWords) { - return text; + return text } const regex = new RegExp( - stopWords.map((word) => `(\\s+)?${word}(\\s+)?`).join("|"), - "gi" - ); - return text.replace(regex, " ").trim(); -}; + stopWords.map((word) => `(\\s+)?${word}(\\s+)?`).join('|'), + 'gi' + ) + return text.replace(regex, ' ').trim() +} export const sort = (query, options) => { - const cleanQuery = clean(query); + const cleanQuery = clean(query) return options .map(cleanseOption) .map((option) => addWeightWithBoost(option, cleanQuery)) .filter(hasWeight) .sort(byWeightThenAlphabetically) - .map(optionName); -}; + .map(optionName) +} export const suggestion = (value, options) => { - const option = options.find((o) => o.name === value); + const option = options.find((o) => o.name === value) if (option) { - const html = `${value}`; - return html; + const html = `${value}` + return html } else { - return `No results found`; + return 'No results found' } -}; +} export const enhanceOption = (option) => { return { name: option.label, - boost: parseFloat(option.getAttribute("data-boost")) || 1, - }; -}; + boost: parseFloat(option.getAttribute('data-boost')) || 1 + } +}