// Thanks to https://github.com/mattyork/fuzzy class TributeSearch { constructor(tribute) { this.tribute = tribute this.tribute.search = this } simpleFilter(pattern, array) { return array.filter(string => { return this.test(pattern, string) }) } test(pattern, string) { return this.match(pattern, string) !== null } match(pattern, string, opts) { opts = opts || {} let patternIdx = 0, result = [], len = string.length, totalScore = 0, currScore = 0, pre = opts.pre || '', post = opts.post || '', compareString = opts.caseSensitive && string || string.toLowerCase(), ch, compareChar if (opts.skip) { return {rendered: string, score: 0} } pattern = opts.caseSensitive && pattern || pattern.toLowerCase() let patternCache = this.traverse(compareString, pattern, 0, 0, []) if (!patternCache) { return null } return { rendered: this.render(string, patternCache.cache, pre, post), score: patternCache.score } } traverse(string, pattern, stringIndex, patternIndex, patternCache) { // if the pattern search at end if (pattern.length === patternIndex) { // calculate score and copy the cache containing the indices where it's found return { score: this.calculateScore(patternCache), cache: patternCache.slice() } } // if string at end or remaining pattern > remaining string if (string.length === stringIndex || pattern.length - patternIndex > string.length - stringIndex) { return undefined } let c = pattern[patternIndex] let index = string.indexOf(c, stringIndex) let best, temp while (index > -1) { patternCache.push(index) temp = this.traverse(string, pattern, index + 1, patternIndex + 1, patternCache) patternCache.pop() // if downstream traversal failed, return best answer so far if (!temp) { return best } if (!best || best.score < temp.score) { best = temp } index = string.indexOf(c, index + 1) } return best } calculateScore(patternCache) { let score = 0 let temp = 1 patternCache.forEach((index, i) => { if (i > 0) { if (patternCache[i - 1] + 1 === index) { temp += temp + 1 } else { temp = 1 } } score += temp }) return score } render(string, indices, pre, post) { var rendered = string.substring(0, indices[0]) indices.forEach((index, i) => { rendered += pre + string[index] + post + string.substring(index + 1, (indices[i + 1]) ? indices[i + 1] : string.length) }) return rendered } filter(pattern, arr, opts) { opts = opts || {} return arr .reduce((prev, element, idx, arr) => { let str = element if (opts.extract) { str = opts.extract(element) if (!str) { // take care of undefineds / nulls / etc. str = '' } } let rendered = this.match(pattern, str, opts) if (rendered != null) { prev[prev.length] = { string: rendered.rendered, score: rendered.score, index: idx, original: element } } return prev }, []) .sort((a, b) => { let compare = b.score - a.score if (compare) return compare return a.index - b.index }) } } export default TributeSearch;