const addWeightWithBoost = (option, query) => { option.weight = calculateWeight(option.clean, query) * option.clean.boost return option } const clean = (text) => text .trim() .replace(/['’]/g, '') .replace(/[.,"/#!$%^&*;:{}=\-_`~()]/g, ' ') .toLowerCase() const cleanseOption = (option) => { option.clean = { name: clean(option.name), nameWithoutStopWords: removeStopWords(option.name), boost: option.boost || 1 } return option } 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 return 0 } 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 wordsStartsWithQuery = (word, regExps) => regExps.every((regExp) => word.search(regExp) >= 0) const calculateWeight = ({ name, nameWithoutStopWords }, query) => { const queryWithoutStopWords = removeStopWords(query) if (exactMatch(name, query)) return 100 if (exactMatch(nameWithoutStopWords, queryWithoutStopWords)) return 95 if (startsWith(name, query)) return 60 if (startsWith(nameWithoutStopWords, queryWithoutStopWords)) return 55 const startsWithRegExps = queryWithoutStopWords .split(/\s+/) .map(startsWithRegExp) if (wordsStartsWithQuery(nameWithoutStopWords, startsWithRegExps)) return 25 return 0 } const stopWords = ['the', 'of', 'in', 'and', 'at', '&'] const removeStopWords = (text) => { const isAllStopWords = text .trim() .split(' ') .every((word) => stopWords.includes(word)) if (isAllStopWords) { return text } const regex = new RegExp( stopWords.map((word) => `(\\s+)?${word}(\\s+)?`).join('|'), 'gi' ) return text.replace(regex, ' ').trim() } export const sort = (query, options) => { const cleanQuery = clean(query) return options .map(cleanseOption) .map((option) => addWeightWithBoost(option, cleanQuery)) .filter(hasWeight) .sort(byWeightThenAlphabetically) .map(optionName) } export const suggestion = (value, options) => { const option = options.find((o) => o.name === value) if (option) { const html = `${value}` return html } else { return 'No results found' } } export const enhanceOption = (option) => { return { name: option.label, boost: parseFloat(option.getAttribute('data-boost')) || 1 } }