Browse Source

lint

pull/728/head
Kat 3 years ago
parent
commit
6876755219
  1. 32
      app/frontend/controllers/accessible_autocomplete_controller.js
  2. 102
      app/frontend/modules/search.js

32
app/frontend/controllers/accessible_autocomplete_controller.js

@ -1,24 +1,24 @@
import { Controller } from "@hotwired/stimulus"; import { Controller } from '@hotwired/stimulus'
import accessibleAutocomplete from "accessible-autocomplete"; import accessibleAutocomplete from 'accessible-autocomplete'
import "accessible-autocomplete/dist/accessible-autocomplete.min.css"; import 'accessible-autocomplete/dist/accessible-autocomplete.min.css'
import { enhanceOption, suggestion, sort } from "../modules/search"; import { enhanceOption, suggestion, sort } from '../modules/search'
export default class extends Controller { export default class extends Controller {
connect() { connect () {
const selectEl = this.element; const selectEl = this.element
const selectOptions = Array.from(selectEl.options); const selectOptions = Array.from(selectEl.options)
const options = selectOptions.map((o) => enhanceOption(o)); const options = selectOptions.map((o) => enhanceOption(o))
const matches = /^(\w+)\[(\w+)\]$/.exec(selectEl.name); const matches = /^(\w+)\[(\w+)\]$/.exec(selectEl.name)
const rawFieldName = `${matches[1]}[${matches[2]}_raw]`; const rawFieldName = `${matches[1]}[${matches[2]}_raw]`
accessibleAutocomplete.enhanceSelectElement({ accessibleAutocomplete.enhanceSelectElement({
defaultValue: "", defaultValue: '',
selectElement: selectEl, selectElement: selectEl,
minLength: 2, minLength: 2,
source: (query, populateResults) => { source: (query, populateResults) => {
if (/\S/.test(query)) { if (/\S/.test(query)) {
populateResults(sort(query, options)); populateResults(sort(query, options))
} }
}, },
autoselect: true, autoselect: true,
@ -28,9 +28,9 @@ export default class extends Controller {
const selectedOption = [].filter.call( const selectedOption = [].filter.call(
selectOptions, selectOptions,
(option) => (option.textContent || option.innerText) === val (option) => (option.textContent || option.innerText) === val
)[0]; )[0]
if (selectedOption) selectedOption.selected = true; if (selectedOption) selectedOption.selected = true
}, }
}); })
} }
} }

102
app/frontend/modules/search.js

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

Loading…
Cancel
Save