You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
106 lines
2.6 KiB
106 lines
2.6 KiB
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 = `<span>${value}</span>` |
|
return html |
|
} else { |
|
return '<span>No results found</span>' |
|
} |
|
} |
|
|
|
export const enhanceOption = (option) => { |
|
return { |
|
name: option.label, |
|
boost: parseFloat(option.getAttribute('data-boost')) || 1 |
|
} |
|
}
|
|
|