Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@orchidjs/sifter

Package Overview
Dependencies
Maintainers
1
Versions
19
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@orchidjs/sifter - npm Package Compare versions

Comparing version 0.9.3 to 1.0.0

dist/esm/lib/sifter.js

825

dist/cjs/sifter.js
/*! sifter.js | https://github.com/orchidjs/sifter.js | Apache License (v2) */
'use strict';
var utils = require('./utils.js');
var diacritics = require('./diacritics.js');
Object.defineProperty(exports, '__esModule', { value: true });
/*! @orchidjs/unicode-variants | https://github.com/orchidjs/unicode-variants | Apache License (v2) */
/**
* Convert array of strings to a regular expression
* ex ['ab','a'] => (?:ab|a)
* ex ['a','b'] => [ab]
* @param {string[]} chars
* @return {string}
*/
const arrayToPattern = chars => {
chars = chars.filter(Boolean);
if (chars.length < 2) {
return chars[0] || '';
}
return maxValueLength(chars) == 1 ? '[' + chars.join('') + ']' : '(?:' + chars.join('|') + ')';
};
/**
* @param {string[]} array
* @return {string}
*/
const sequencePattern = array => {
if (!hasDuplicates(array)) {
return array.join('');
}
let pattern = '';
let prev_char_count = 0;
const prev_pattern = () => {
if (prev_char_count > 1) {
pattern += '{' + prev_char_count + '}';
}
};
array.forEach((char, i) => {
if (char === array[i - 1]) {
prev_char_count++;
return;
}
prev_pattern();
pattern += char;
prev_char_count = 1;
});
prev_pattern();
return pattern;
};
/**
* Convert array of strings to a regular expression
* ex ['ab','a'] => (?:ab|a)
* ex ['a','b'] => [ab]
* @param {Set<string>} chars
* @return {string}
*/
const setToPattern = chars => {
let array = toArray(chars);
return arrayToPattern(array);
};
/**
*
* https://stackoverflow.com/questions/7376598/in-javascript-how-do-i-check-if-an-array-has-duplicate-values
* @param {any[]} array
*/
const hasDuplicates = array => {
return new Set(array).size !== array.length;
};
/**
* https://stackoverflow.com/questions/63006601/why-does-u-throw-an-invalid-escape-error
* @param {string} str
* @return {string}
*/
const escape_regex = str => {
return (str + '').replace(/([\$\(-\+\.\?\[-\^\{-\}])/g, '\\$1');
};
/**
* Return the max length of array values
* @param {string[]} array
*
*/
const maxValueLength = array => {
return array.reduce((longest, value) => Math.max(longest, unicodeLength(value)), 0);
};
/**
* @param {string} str
*/
const unicodeLength = str => {
return toArray(str).length;
};
/**
* @param {any} p
* @return {any[]}
*/
const toArray = p => Array.from(p);
/*! @orchidjs/unicode-variants | https://github.com/orchidjs/unicode-variants | Apache License (v2) */
/**
* Get all possible combinations of substrings that add up to the given string
* https://stackoverflow.com/questions/30169587/find-all-the-combination-of-substrings-that-add-up-to-the-given-string
* @param {string} input
* @return {string[][]}
*/
const allSubstrings = input => {
if (input.length === 1) return [[input]];
/** @type {string[][]} */
let result = [];
const start = input.substring(1);
const suba = allSubstrings(start);
suba.forEach(function (subresult) {
let tmp = subresult.slice(0);
tmp[0] = input.charAt(0) + tmp[0];
result.push(tmp);
tmp = subresult.slice(0);
tmp.unshift(input.charAt(0));
result.push(tmp);
});
return result;
};
/*! @orchidjs/unicode-variants | https://github.com/orchidjs/unicode-variants | Apache License (v2) */
/**
* @typedef {{[key:string]:string}} TUnicodeMap
* @typedef {{[key:string]:Set<string>}} TUnicodeSets
* @typedef {[[number,number]]} TCodePoints
* @typedef {{folded:string,composed:string,code_point:number}} TCodePointObj
* @typedef {{start:number,end:number,length:number,substr:string}} TSequencePart
*/
/** @type {TCodePoints} */
const code_points = [[0, 65535]];
const accent_pat = '[\u0300-\u036F\u{b7}\u{2be}]'; // \u{2bc}
/** @type {TUnicodeMap} */
let unicode_map;
/** @type {RegExp} */
let multi_char_reg;
const max_char_length = 3;
/** @type {TUnicodeMap} */
const latin_convert = {
'æ': 'ae',
'ⱥ': 'a',
'ø': 'o',
'⁄': '/',
'∕': '/'
};
const convert_pat = new RegExp(Object.keys(latin_convert).join('|') + '|' + accent_pat, 'gu');
/**
* Initialize the unicode_map from the give code point ranges
*
* @param {TCodePoints=} _code_points
*/
const initialize = _code_points => {
if (unicode_map !== undefined) return;
unicode_map = generateMap(_code_points || code_points);
};
/**
* Helper method for normalize a string
* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/normalize
* @param {string} str
* @param {string} form
*/
const normalize = (str, form = 'NFKD') => str.normalize(form);
/**
* Compatibility Decomposition without reordering string
* calling str.normalize('NFKD') on \u{594}\u{595}\u{596} becomes \u{596}\u{594}\u{595}
* @param {string} str
*/
const decompose = str => {
if (str.match(/[\u0f71-\u0f81]/)) {
return toArray(str).reduce(
/**
* @param {string} result
* @param {string} char
*/
(result, char) => {
return result + normalize(char);
}, '');
}
return normalize(str);
};
/**
* Remove accents
* via https://github.com/krisk/Fuse/issues/133#issuecomment-318692703
* @param {string} str
* @return {string}
*/
const asciifold = str => {
return decompose(str).toLowerCase().replace(convert_pat,
/** @type {string} */
char => {
return latin_convert[char] || '';
});
};
/**
* Generate a list of unicode variants from the list of code points
* @param {TCodePoints} code_points
* @yield {TCodePointObj}
*/
function* generator(code_points) {
for (const [code_point_min, code_point_max] of code_points) {
for (let i = code_point_min; i <= code_point_max; i++) {
let composed = String.fromCharCode(i);
let folded = asciifold(composed);
if (folded == composed.toLowerCase()) {
continue;
} // skip when folded is a string longer than 3 characters long
// bc the resulting regex patterns will be long
// eg:
// folded صلى الله عليه وسلم length 18 code point 65018
// folded جل جلاله length 8 code point 65019
if (folded.length > max_char_length) {
continue;
}
if (folded.length == 0) {
continue;
}
let decomposed = normalize(composed);
let recomposed = normalize(decomposed, 'NFC');
if (recomposed === composed && folded === decomposed) {
continue;
}
yield {
folded: folded,
composed: composed,
code_point: i
};
}
}
}
/**
* Generate a unicode map from the list of code points
* @param {TCodePoints} code_points
* @return {TUnicodeSets}
*/
const generateSets = code_points => {
/** @type {{[key:string]:Set<string>}} */
const unicode_sets = {};
/**
* @param {string} folded
* @param {string} to_add
*/
const addMatching = (folded, to_add) => {
/** @type {Set<string>} */
const folded_set = unicode_sets[folded] || new Set();
const patt = new RegExp('^' + setToPattern(folded_set) + '$', 'iu');
if (to_add.match(patt)) {
return;
}
folded_set.add(escape_regex(to_add));
unicode_sets[folded] = folded_set;
};
for (let value of generator(code_points)) {
addMatching(value.folded, value.folded);
addMatching(value.folded, value.composed);
}
return unicode_sets;
};
/**
* Generate a unicode map from the list of code points
* ae => (?:(?:ae|Æ|Ǽ|Ǣ)|(?:A|Ⓐ|A...)(?:E|ɛ|Ⓔ...))
*
* @param {TCodePoints} code_points
* @return {TUnicodeMap}
*/
const generateMap = code_points => {
/** @type {TUnicodeSets} */
const unicode_sets = generateSets(code_points);
/** @type {TUnicodeMap} */
const unicode_map = {};
/** @type {string[]} */
let multi_char = [];
for (let folded in unicode_sets) {
let set = unicode_sets[folded];
if (set) {
unicode_map[folded] = setToPattern(set);
}
if (folded.length > 1) {
multi_char.push(escape_regex(folded));
}
}
multi_char.sort((a, b) => b.length - a.length);
const multi_char_patt = arrayToPattern(multi_char);
multi_char_reg = new RegExp('^' + multi_char_patt, 'u');
return unicode_map;
};
/**
* Map each element of an array from it's folded value to all possible unicode matches
* @param {string[]} strings
* @param {number} min_replacement
* @return {string}
*/
const mapSequence = (strings, min_replacement = 1) => {
let chars_replaced = 0;
strings = strings.map(str => {
if (unicode_map[str]) {
chars_replaced += str.length;
}
return unicode_map[str] || str;
});
if (chars_replaced >= min_replacement) {
return sequencePattern(strings);
}
return '';
};
/**
* Convert a short string and split it into all possible patterns
* Keep a pattern only if min_replacement is met
*
* 'abc'
* => [['abc'],['ab','c'],['a','bc'],['a','b','c']]
* => ['abc-pattern','ab-c-pattern'...]
*
*
* @param {string} str
* @param {number} min_replacement
* @return {string}
*/
const substringsToPattern = (str, min_replacement = 1) => {
min_replacement = Math.max(min_replacement, str.length - 1);
return arrayToPattern(allSubstrings(str).map(sub_pat => {
return mapSequence(sub_pat, min_replacement);
}));
};
/**
* Convert an array of sequences into a pattern
* [{start:0,end:3,length:3,substr:'iii'}...] => (?:iii...)
*
* @param {Sequence[]} sequences
* @param {boolean} all
*/
const sequencesToPattern = (sequences, all = true) => {
let min_replacement = sequences.length > 1 ? 1 : 0;
return arrayToPattern(sequences.map(sequence => {
let seq = [];
const len = all ? sequence.length() : sequence.length() - 1;
for (let j = 0; j < len; j++) {
seq.push(substringsToPattern(sequence.substrs[j] || '', min_replacement));
}
return sequencePattern(seq);
}));
};
/**
* Return true if the sequence is already in the sequences
* @param {Sequence} needle_seq
* @param {Sequence[]} sequences
*/
const inSequences = (needle_seq, sequences) => {
for (const seq of sequences) {
if (seq.start != needle_seq.start || seq.end != needle_seq.end) {
continue;
}
if (seq.substrs.join('') !== needle_seq.substrs.join('')) {
continue;
}
let needle_parts = needle_seq.parts;
/**
* @param {TSequencePart} part
*/
const filter = part => {
for (const needle_part of needle_parts) {
if (needle_part.start === part.start && needle_part.substr === part.substr) {
return false;
}
if (part.length == 1 || needle_part.length == 1) {
continue;
} // check for overlapping parts
// a = ['::=','==']
// b = ['::','===']
// a = ['r','sm']
// b = ['rs','m']
if (part.start < needle_part.start && part.end > needle_part.start) {
return true;
}
if (needle_part.start < part.start && needle_part.end > part.start) {
return true;
}
}
return false;
};
let filtered = seq.parts.filter(filter);
if (filtered.length > 0) {
continue;
}
return true;
}
return false;
};
class Sequence {
constructor() {
/** @type {TSequencePart[]} */
this.parts = [];
/** @type {string[]} */
this.substrs = [];
this.start = 0;
this.end = 0;
}
/**
* @param {TSequencePart|undefined} part
*/
add(part) {
if (part) {
this.parts.push(part);
this.substrs.push(part.substr);
this.start = Math.min(part.start, this.start);
this.end = Math.max(part.end, this.end);
}
}
last() {
return this.parts[this.parts.length - 1];
}
length() {
return this.parts.length;
}
/**
* @param {number} position
* @param {TSequencePart} last_piece
*/
clone(position, last_piece) {
let clone = new Sequence();
let parts = JSON.parse(JSON.stringify(this.parts));
let last_part = parts.pop();
for (const part of parts) {
clone.add(part);
}
let last_substr = last_piece.substr.substring(0, position - last_part.start);
let clone_last_len = last_substr.length;
clone.add({
start: last_part.start,
end: last_part.start + clone_last_len,
length: clone_last_len,
substr: last_substr
});
return clone;
}
}
/**
* Expand a regular expression pattern to include unicode variants
* eg /a/ becomes /aⓐaẚàáâầấẫẩãāăằắẵẳȧǡäǟảåǻǎȁȃạậặḁąⱥɐɑAⒶAÀÁÂẦẤẪẨÃĀĂẰẮẴẲȦǠÄǞẢÅǺǍȀȂẠẬẶḀĄȺⱯ/
*
* Issue:
* ﺊﺋ [ 'ﺊ = \\u{fe8a}', 'ﺋ = \\u{fe8b}' ]
* becomes: ئئ [ 'ي = \\u{64a}', 'ٔ = \\u{654}', 'ي = \\u{64a}', 'ٔ = \\u{654}' ]
*
* İIJ = IIJ = ⅡJ
*
* 1/2/4
*
* @param {string} str
* @return {string|undefined}
*/
const getPattern = str => {
initialize();
str = asciifold(str);
let pattern = '';
let sequences = [new Sequence()];
for (let i = 0; i < str.length; i++) {
let substr = str.substring(i);
let match = substr.match(multi_char_reg);
const char = str.substring(i, i + 1);
const match_str = match ? match[0] : null; // loop through sequences
// add either the char or multi_match
let overlapping = [];
let added_types = new Set();
for (const sequence of sequences) {
const last_piece = sequence.last();
if (!last_piece || last_piece.length == 1 || last_piece.end <= i) {
// if we have a multi match
if (match_str) {
const len = match_str.length;
sequence.add({
start: i,
end: i + len,
length: len,
substr: match_str
});
added_types.add('1');
} else {
sequence.add({
start: i,
end: i + 1,
length: 1,
substr: char
});
added_types.add('2');
}
} else if (match_str) {
let clone = sequence.clone(i, last_piece);
const len = match_str.length;
clone.add({
start: i,
end: i + len,
length: len,
substr: match_str
});
overlapping.push(clone);
} else {
// don't add char
// adding would create invalid patterns: 234 => [2,34,4]
added_types.add('3');
}
} // if we have overlapping
if (overlapping.length > 0) {
// ['ii','iii'] before ['i','i','iii']
overlapping = overlapping.sort((a, b) => {
return a.length() - b.length();
});
for (let clone of overlapping) {
// don't add if we already have an equivalent sequence
if (inSequences(clone, sequences)) {
continue;
}
sequences.push(clone);
}
continue;
} // if we haven't done anything unique
// clean up the patterns
// helps keep patterns smaller
// if str = 'r₨㎧aarss', pattern will be 446 instead of 655
if (i > 0 && added_types.size == 1 && !added_types.has('3')) {
pattern += sequencesToPattern(sequences, false);
let new_seq = new Sequence();
const old_seq = sequences[0];
if (old_seq) {
new_seq.add(old_seq.last());
}
sequences = [new_seq];
}
}
pattern += sequencesToPattern(sequences, true);
return pattern;
};
/**
* A property getter resolving dot-notation
* @param {Object} obj The root object to fetch property on
* @param {String} name The optionally dotted property name to fetch
* @return {Object} The resolved property value
*/
const getAttr = (obj, name) => {
if (!obj) return;
return obj[name];
};
/**
* A property getter resolving dot-notation
* @param {Object} obj The root object to fetch property on
* @param {String} name The optionally dotted property name to fetch
* @return {Object} The resolved property value
*/
const getAttrNesting = (obj, name) => {
if (!obj) return;
var part,
names = name.split(".");
while ((part = names.shift()) && (obj = obj[part]));
return obj;
};
/**
* Calculates how close of a match the
* given value is against a search token.
*
*/
const scoreValue = (value, token, weight) => {
var score, pos;
if (!value) return 0;
value = value + '';
if (token.regex == null) return 0;
pos = value.search(token.regex);
if (pos === -1) return 0;
score = token.string.length / value.length;
if (pos === 0) score += 0.5;
return score * weight;
};
/**
* Cast object property to an array if it exists and has a value
*
*/
const propToArray = (obj, key) => {
var value = obj[key];
if (typeof value == 'function') return value;
if (value && !Array.isArray(value)) {
obj[key] = [value];
}
};
/**
* Iterates over arrays and hashes.
*
* ```
* iterate(this.items, function(item, id) {
* // invoked for each item
* });
* ```
*
*/
const iterate = (object, callback) => {
if (Array.isArray(object)) {
object.forEach(callback);
} else {
for (var key in object) {
if (object.hasOwnProperty(key)) {
callback(object[key], key);
}
}
}
};
const cmp = (a, b) => {
if (typeof a === 'number' && typeof b === 'number') {
return a > b ? 1 : a < b ? -1 : 0;
}
a = asciifold(a + '').toLowerCase();
b = asciifold(b + '').toLowerCase();
if (a > b) return 1;
if (b > a) return -1;
return 0;
};
/**
* sifter.js

@@ -53,3 +779,3 @@ * Copyright (c) 2013–2020 Brian Reavis & contributors

if (weights) {
field_regex = new RegExp('^(' + Object.keys(weights).map(utils.escape_regex).join('|') + ')\:(.*)$');
field_regex = new RegExp('^(' + Object.keys(weights).map(escape_regex).join('|') + ')\:(.*)$');
}

@@ -69,8 +795,8 @@

if (this.settings.diacritics) {
regex = diacritics.diacriticRegexPoints(word);
regex = getPattern(word) || null;
} else {
regex = utils.escape_regex(word);
regex = escape_regex(word);
}
if (respect_word_boundaries) regex = "\\b" + regex;
if (regex && respect_word_boundaries) regex = "\\b" + regex;
}

@@ -93,3 +819,3 @@

*
* @returns {function}
* @returns {T.ScoreFn}
*/

@@ -100,3 +826,8 @@ getScoreFunction(query, options) {

}
/**
* @returns {T.ScoreFn}
*
*/
_getScoreFunction(search) {

@@ -133,3 +864,3 @@ const tokens = search.tokens,

const field = fields[0].field;
return utils.scoreValue(getAttrFn(data, field), token, weights[field]);
return scoreValue(getAttrFn(data, field), token, weights[field] || 1);
};

@@ -147,7 +878,7 @@ }

} else {
sum += utils.scoreValue(value, token, 1);
sum += scoreValue(value, token, 1);
}
} else {
utils.iterate(weights, (weight, field) => {
sum += utils.scoreValue(getAttrFn(data, field), token, weight);
iterate(weights, (weight, field) => {
sum += scoreValue(getAttrFn(data, field), token, weight);
});

@@ -168,8 +899,7 @@ }

return function (data) {
var i = 0,
score,
var score,
sum = 0;
for (; i < token_count; i++) {
score = scoreObject(tokens[i], data);
for (let token of tokens) {
score = scoreObject(token, data);
if (score <= 0) return 0;

@@ -184,3 +914,3 @@ sum += score;

var sum = 0;
utils.iterate(tokens, token => {
iterate(tokens, token => {
sum += scoreObject(token, data);

@@ -206,8 +936,7 @@ });

_getSortFunction(search) {
var i, n, implicit_score;
var implicit_score,
sort_flds = [];
const self = this,
options = search.options,
sort = !search.query && options.sort_empty ? options.sort_empty : options.sort,
sort_flds = [],
multipliers = [];
sort = !search.query && options.sort_empty ? options.sort_empty : options.sort;

@@ -231,5 +960,5 @@ if (typeof sort == 'function') {

if (sort) {
for (i = 0, n = sort.length; i < n; i++) {
if (search.query || sort[i].field !== '$score') {
sort_flds.push(sort[i]);
for (let s of sort) {
if (search.query || s.field !== '$score') {
sort_flds.push(s);
}

@@ -244,4 +973,4 @@ }

for (i = 0, n = sort_flds.length; i < n; i++) {
if (sort_flds[i].field === '$score') {
for (let fld of sort_flds) {
if (fld.field === '$score') {
implicit_score = false;

@@ -257,10 +986,6 @@ break;

});
}
} // without a search.query, all items will have the same score
} else {
for (i = 0, n = sort_flds.length; i < n; i++) {
if (sort_flds[i].field === '$score') {
sort_flds.splice(i, 1);
break;
}
}
sort_flds = sort_flds.filter(fld => fld.field !== '$score');
} // build function

@@ -276,13 +1001,8 @@

return function (a, b) {
var i, result, field;
var result, field;
for (i = 0; i < sort_flds_count; i++) {
field = sort_flds[i].field;
let multiplier = multipliers[i];
if (multiplier == undefined) {
multiplier = sort_flds[i].direction === 'desc' ? -1 : 1;
}
result = multiplier * utils.cmp(get_field(field, a), get_field(field, b));
for (let sort_fld of sort_flds) {
field = sort_fld.field;
let multiplier = sort_fld.direction === 'desc' ? -1 : 1;
result = multiplier * cmp(get_field(field, a), get_field(field, b));
if (result) return result;

@@ -304,7 +1024,7 @@ }

var options = Object.assign({}, optsUser);
utils.propToArray(options, 'sort');
utils.propToArray(options, 'sort_empty'); // convert fields to new format
propToArray(options, 'sort');
propToArray(options, 'sort_empty'); // convert fields to new format
if (options.fields) {
utils.propToArray(options, 'fields');
propToArray(options, 'fields');
const fields = [];

@@ -332,3 +1052,3 @@ options.fields.forEach(field => {

weights: weights,
getAttrFn: options.nesting ? utils.getAttrNesting : utils.getAttr
getAttrFn: options.nesting ? getAttrNesting : getAttr
};

@@ -353,3 +1073,3 @@ }

if (query.length) {
utils.iterate(self.items, (item, id) => {
iterate(self.items, (item, id) => {
score = fn_score(item);

@@ -365,3 +1085,3 @@

} else {
utils.iterate(self.items, (_, id) => {
iterate(self.items, (_, id) => {
search.items.push({

@@ -389,3 +1109,10 @@ 'score': 1,

module.exports = Sifter;
exports.Sifter = Sifter;
exports.cmp = cmp;
exports.getAttr = getAttr;
exports.getAttrNesting = getAttrNesting;
exports.getPattern = getPattern;
exports.iterate = iterate;
exports.propToArray = propToArray;
exports.scoreValue = scoreValue;
//# sourceMappingURL=sifter.js.map

68

dist/esm/sifter.js
/*! sifter.js | https://github.com/orchidjs/sifter.js | Apache License (v2) */
import { escape_regex, iterate, cmp, propToArray, getAttrNesting, getAttr, scoreValue } from './utils.js';
import { diacriticRegexPoints } from './diacritics.js';
import { iterate, cmp, propToArray, getAttrNesting, getAttr, scoreValue } from './utils.js';
export { cmp, getAttr, getAttrNesting, iterate, propToArray, scoreValue } from './utils.js';
import { escape_regex, getPattern } from '@orchidjs/unicode-variants';
export { getPattern } from '@orchidjs/unicode-variants';

@@ -66,3 +68,3 @@ /**

if (this.settings.diacritics) {
regex = diacriticRegexPoints(word);
regex = getPattern(word) || null;
} else {

@@ -72,3 +74,3 @@ regex = escape_regex(word);

if (respect_word_boundaries) regex = "\\b" + regex;
if (regex && respect_word_boundaries) regex = "\\b" + regex;
}

@@ -91,3 +93,3 @@

*
* @returns {function}
* @returns {T.ScoreFn}
*/

@@ -98,3 +100,8 @@ getScoreFunction(query, options) {

}
/**
* @returns {T.ScoreFn}
*
*/
_getScoreFunction(search) {

@@ -131,3 +138,3 @@ const tokens = search.tokens,

const field = fields[0].field;
return scoreValue(getAttrFn(data, field), token, weights[field]);
return scoreValue(getAttrFn(data, field), token, weights[field] || 1);
};

@@ -165,8 +172,7 @@ }

return function (data) {
var i = 0,
score,
var score,
sum = 0;
for (; i < token_count; i++) {
score = scoreObject(tokens[i], data);
for (let token of tokens) {
score = scoreObject(token, data);
if (score <= 0) return 0;

@@ -202,8 +208,7 @@ sum += score;

_getSortFunction(search) {
var i, n, implicit_score;
var implicit_score,
sort_flds = [];
const self = this,
options = search.options,
sort = !search.query && options.sort_empty ? options.sort_empty : options.sort,
sort_flds = [],
multipliers = [];
sort = !search.query && options.sort_empty ? options.sort_empty : options.sort;

@@ -227,5 +232,5 @@ if (typeof sort == 'function') {

if (sort) {
for (i = 0, n = sort.length; i < n; i++) {
if (search.query || sort[i].field !== '$score') {
sort_flds.push(sort[i]);
for (let s of sort) {
if (search.query || s.field !== '$score') {
sort_flds.push(s);
}

@@ -240,4 +245,4 @@ }

for (i = 0, n = sort_flds.length; i < n; i++) {
if (sort_flds[i].field === '$score') {
for (let fld of sort_flds) {
if (fld.field === '$score') {
implicit_score = false;

@@ -253,10 +258,6 @@ break;

});
}
} // without a search.query, all items will have the same score
} else {
for (i = 0, n = sort_flds.length; i < n; i++) {
if (sort_flds[i].field === '$score') {
sort_flds.splice(i, 1);
break;
}
}
sort_flds = sort_flds.filter(fld => fld.field !== '$score');
} // build function

@@ -272,12 +273,7 @@

return function (a, b) {
var i, result, field;
var result, field;
for (i = 0; i < sort_flds_count; i++) {
field = sort_flds[i].field;
let multiplier = multipliers[i];
if (multiplier == undefined) {
multiplier = sort_flds[i].direction === 'desc' ? -1 : 1;
}
for (let sort_fld of sort_flds) {
field = sort_fld.field;
let multiplier = sort_fld.direction === 'desc' ? -1 : 1;
result = multiplier * cmp(get_field(field, a), get_field(field, b));

@@ -381,3 +377,3 @@ if (result) return result;

export default Sifter;
export { Sifter };
//# sourceMappingURL=sifter.js.map
/*! sifter.js | https://github.com/orchidjs/sifter.js | Apache License (v2) */
import { asciifold } from './diacritics.js';
import { asciifold } from '@orchidjs/unicode-variants';
// @ts-ignore TS2691 "An import path cannot end with a '.ts' extension"
/**

@@ -42,2 +40,3 @@ * A property getter resolving dot-notation

value = value + '';
if (token.regex == null) return 0;
pos = value.search(token.regex);

@@ -50,10 +49,2 @@ if (pos === -1) return 0;

/**
*
* https://stackoverflow.com/questions/63006601/why-does-u-throw-an-invalid-escape-error
*/
const escape_regex = str => {
return (str + '').replace(/([\$\(-\+\.\?\[-\^\{-\}])/g, '\\$1');
};
/**
* Cast object property to an array if it exists and has a value

@@ -105,3 +96,3 @@ *

export { cmp, escape_regex, getAttr, getAttrNesting, iterate, propToArray, scoreValue };
export { cmp, getAttr, getAttrNesting, iterate, propToArray, scoreValue };
//# sourceMappingURL=utils.js.map

@@ -16,4 +16,6 @@ /**

*/
import * as T from 'types.ts';
export default class Sifter {
import { scoreValue, getAttr, getAttrNesting, propToArray, iterate, cmp } from './utils';
import { getPattern } from '@orchidjs/unicode-variants';
import * as T from './types';
declare class Sifter {
items: any;

@@ -40,7 +42,11 @@ settings: T.Settings;

*
* @returns {function}
* @returns {T.ScoreFn}
*/
getScoreFunction(query: string, options: T.Options): (data: {}) => any;
_getScoreFunction(search: T.PrepareObj): (data: {}) => any;
getScoreFunction(query: string, options: T.Options): (data: {}) => number;
/**
* @returns {T.ScoreFn}
*
*/
_getScoreFunction(search: T.PrepareObj): (data: {}) => number;
/**
* Returns a function that can be used to compare two

@@ -52,4 +58,4 @@ * results, for sorting purposes. If no sorting should

*/
getSortFunction(query: string, options: T.Options): any;
_getSortFunction(search: T.PrepareObj): any;
getSortFunction(query: string, options: Partial<T.Options>): ((a: T.ResultItem, b: T.ResultItem) => number) | null;
_getSortFunction(search: T.PrepareObj): ((a: T.ResultItem, b: T.ResultItem) => number) | null;
/**

@@ -68,1 +74,2 @@ * Parses a search query and returns an object

}
export { Sifter, scoreValue, getAttr, getAttrNesting, propToArray, iterate, cmp, getPattern };

@@ -1,2 +0,2 @@

import Sifter from 'sifter.ts';
import { Sifter } from './sifter';
export declare type Field = {

@@ -13,10 +13,10 @@ field: string;

fields: Field[];
score: () => any;
filter: boolean;
limit: number;
conjunction: string;
sort: SortFn | Sort[];
sort_empty: SortFn | Sort[];
nesting: boolean;
respect_word_boundaries: boolean;
conjunction: string;
score?: ScoreFn;
filter?: boolean;
sort_empty?: SortFn | Sort[];
respect_word_boundaries?: boolean;
limit?: number;
};

@@ -47,1 +47,2 @@ export declare type Token = {

};
export declare type ScoreFn = (item: ResultItem) => number;

@@ -0,1 +1,2 @@

import * as T from './types';
/**

@@ -28,7 +29,2 @@ * A property getter resolving dot-notation

/**
*
* https://stackoverflow.com/questions/63006601/why-does-u-throw-an-invalid-escape-error
*/
export declare const escape_regex: (str: string) => string;
/**
* Cast object property to an array if it exists and has a value

@@ -52,3 +48,3 @@ *

[key: string]: any;
}, callback: (value: any, key: number | string) => any) => void;
}, callback: (value: any, key: any) => any) => void;
export declare const cmp: (a: number | string, b: number | string) => 1 | -1 | 0;
/*! sifter.js | https://github.com/orchidjs/sifter.js | Apache License (v2) */
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
typeof define === 'function' && define.amd ? define(factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.sifter = factory());
}(this, (function () { 'use strict';
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
typeof define === 'function' && define.amd ? define(['exports'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.sifter = {}));
}(this, (function (exports) { 'use strict';
// @ts-ignore TS2691 "An import path cannot end with a '.ts' extension"
// https://github.com/andrewrk/node-diacritics/blob/master/index.js
var latin_pat;
const accent_pat = '[\u0300-\u036F\u{b7}\u{2be}]'; // \u{2bc}
/*! @orchidjs/unicode-variants | https://github.com/orchidjs/unicode-variants | Apache License (v2) */
const accent_reg = new RegExp(accent_pat, 'gu');
var diacritic_patterns;
const latin_convert = {
'æ': 'ae',
'ⱥ': 'a',
'ø': 'o'
};
const convert_pat = new RegExp(Object.keys(latin_convert).join('|'), 'gu');
const code_points = [[0, 65535]];
/**
* Remove accents
* via https://github.com/krisk/Fuse/issues/133#issuecomment-318692703
*
*/
/**
* Convert array of strings to a regular expression
* ex ['ab','a'] => (?:ab|a)
* ex ['a','b'] => [ab]
* @param {string[]} chars
* @return {string}
*/
const arrayToPattern = chars => {
chars = chars.filter(Boolean);
const asciifold = str => {
return str.normalize('NFKD').replace(accent_reg, '').toLowerCase().replace(convert_pat, function (foreignletter) {
return latin_convert[foreignletter] || foreignletter;
});
};
/**
* Convert array of strings to a regular expression
* ex ['ab','a'] => (?:ab|a)
* ex ['a','b'] => [ab]
*
*/
if (chars.length < 2) {
return chars[0] || '';
}
const arrayToPattern = (chars, glue = '|') => {
if (chars.length === 1 && chars[0] != undefined) {
return chars[0];
}
return maxValueLength(chars) == 1 ? '[' + chars.join('') + ']' : '(?:' + chars.join('|') + ')';
};
/**
* @param {string[]} array
* @return {string}
*/
var longest = 1;
chars.forEach(a => {
longest = Math.max(longest, a.length);
});
if (longest == 1) {
return '[' + chars.join('') + ']';
}
const sequencePattern = array => {
if (!hasDuplicates(array)) {
return array.join('');
}
return '(?:' + chars.join(glue) + ')';
};
const escapeToPattern = chars => {
const escaped = chars.map(diacritic => escape_regex(diacritic));
return arrayToPattern(escaped);
};
/**
* Get all possible combinations of substrings that add up to the given string
* https://stackoverflow.com/questions/30169587/find-all-the-combination-of-substrings-that-add-up-to-the-given-string
*
*/
let pattern = '';
let prev_char_count = 0;
const allSubstrings = input => {
if (input.length === 1) return [[input]];
var result = [];
allSubstrings(input.substring(1)).forEach(function (subresult) {
var tmp = subresult.slice(0);
tmp[0] = input.charAt(0) + tmp[0];
result.push(tmp);
tmp = subresult.slice(0);
tmp.unshift(input.charAt(0));
result.push(tmp);
});
return result;
};
/**
* Generate a list of diacritics from the list of code points
*
*/
const prev_pattern = () => {
if (prev_char_count > 1) {
pattern += '{' + prev_char_count + '}';
}
};
const generateDiacritics = code_points => {
var diacritics = {};
code_points.forEach(code_range => {
for (let i = code_range[0]; i <= code_range[1]; i++) {
let diacritic = String.fromCharCode(i);
let latin = asciifold(diacritic);
array.forEach((char, i) => {
if (char === array[i - 1]) {
prev_char_count++;
return;
}
if (latin == diacritic.toLowerCase()) {
continue;
} // skip when latin is a string longer than 3 characters long
// bc the resulting regex patterns will be long
// eg:
// latin صلى الله عليه وسلم length 18 code point 65018
// latin جل جلاله length 8 code point 65019
prev_pattern();
pattern += char;
prev_char_count = 1;
});
prev_pattern();
return pattern;
};
/**
* Convert array of strings to a regular expression
* ex ['ab','a'] => (?:ab|a)
* ex ['a','b'] => [ab]
* @param {Set<string>} chars
* @return {string}
*/
if (latin.length > 3) {
continue;
}
const setToPattern = chars => {
let array = toArray(chars);
return arrayToPattern(array);
};
/**
*
* https://stackoverflow.com/questions/7376598/in-javascript-how-do-i-check-if-an-array-has-duplicate-values
* @param {any[]} array
*/
const latin_diacritics = diacritics[latin] || [latin];
const patt = new RegExp(escapeToPattern(latin_diacritics), 'iu');
if (diacritic.match(patt)) {
continue;
}
const hasDuplicates = array => {
return new Set(array).size !== array.length;
};
/**
* https://stackoverflow.com/questions/63006601/why-does-u-throw-an-invalid-escape-error
* @param {string} str
* @return {string}
*/
latin_diacritics.push(diacritic);
diacritics[latin] = latin_diacritics;
}
}); // filter out if there's only one character in the list
// todo: this may not be needed
Object.keys(diacritics).forEach(latin => {
const latin_diacritics = diacritics[latin] || [];
const escape_regex = str => {
return (str + '').replace(/([\$\(-\+\.\?\[-\^\{-\}])/g, '\\$1');
};
/**
* Return the max length of array values
* @param {string[]} array
*
*/
if (latin_diacritics.length < 2) {
delete diacritics[latin];
}
}); // latin character pattern
// match longer substrings first
let latin_chars = Object.keys(diacritics).sort((a, b) => b.length - a.length);
latin_pat = new RegExp('(' + escapeToPattern(latin_chars) + accent_pat + '*)', 'gu'); // build diacritic patterns
// ae needs:
// (?:(?:ae|Æ|Ǽ|Ǣ)|(?:A|Ⓐ|A...)(?:E|ɛ|Ⓔ...))
const maxValueLength = array => {
return array.reduce((longest, value) => Math.max(longest, unicodeLength(value)), 0);
};
/**
* @param {string} str
*/
var diacritic_patterns = {};
latin_chars.sort((a, b) => a.length - b.length).forEach(latin => {
var substrings = allSubstrings(latin);
var pattern = substrings.map(sub_pat => {
sub_pat = sub_pat.map(l => {
if (diacritics.hasOwnProperty(l)) {
return escapeToPattern(diacritics[l]);
}
return l;
});
return arrayToPattern(sub_pat, '');
});
diacritic_patterns[latin] = arrayToPattern(pattern);
});
return diacritic_patterns;
};
/**
* Expand a regular expression pattern to include diacritics
* eg /a/ becomes /aⓐaẚàáâầấẫẩãāăằắẵẳȧǡäǟảåǻǎȁȃạậặḁąⱥɐɑAⒶAÀÁÂẦẤẪẨÃĀĂẰẮẴẲȦǠÄǞẢÅǺǍȀȂẠẬẶḀĄȺⱯ/
*
*/
const unicodeLength = str => {
return toArray(str).length;
};
/**
* @param {any} p
* @return {any[]}
*/
const diacriticRegexPoints = regex => {
if (diacritic_patterns === undefined) {
diacritic_patterns = generateDiacritics(code_points);
}
const decomposed = regex.normalize('NFKD').toLowerCase();
return decomposed.split(latin_pat).map(part => {
// "ffl" or "ffl"
const no_accent = asciifold(part);
const toArray = p => Array.from(p);
if (no_accent == '') {
return '';
}
/*! @orchidjs/unicode-variants | https://github.com/orchidjs/unicode-variants | Apache License (v2) */
if (diacritic_patterns.hasOwnProperty(no_accent)) {
return diacritic_patterns[no_accent];
}
/**
* Get all possible combinations of substrings that add up to the given string
* https://stackoverflow.com/questions/30169587/find-all-the-combination-of-substrings-that-add-up-to-the-given-string
* @param {string} input
* @return {string[][]}
*/
const allSubstrings = input => {
if (input.length === 1) return [[input]];
/** @type {string[][]} */
return part;
}).join('');
};
let result = [];
const start = input.substring(1);
const suba = allSubstrings(start);
suba.forEach(function (subresult) {
let tmp = subresult.slice(0);
tmp[0] = input.charAt(0) + tmp[0];
result.push(tmp);
tmp = subresult.slice(0);
tmp.unshift(input.charAt(0));
result.push(tmp);
});
return result;
};
// @ts-ignore TS2691 "An import path cannot end with a '.ts' extension"
/*! @orchidjs/unicode-variants | https://github.com/orchidjs/unicode-variants | Apache License (v2) */
/**
* @typedef {{[key:string]:string}} TUnicodeMap
* @typedef {{[key:string]:Set<string>}} TUnicodeSets
* @typedef {[[number,number]]} TCodePoints
* @typedef {{folded:string,composed:string,code_point:number}} TCodePointObj
* @typedef {{start:number,end:number,length:number,substr:string}} TSequencePart
*/
/**
* A property getter resolving dot-notation
* @param {Object} obj The root object to fetch property on
* @param {String} name The optionally dotted property name to fetch
* @return {Object} The resolved property value
*/
const getAttr = (obj, name) => {
if (!obj) return;
return obj[name];
};
/**
* A property getter resolving dot-notation
* @param {Object} obj The root object to fetch property on
* @param {String} name The optionally dotted property name to fetch
* @return {Object} The resolved property value
*/
/** @type {TCodePoints} */
const getAttrNesting = (obj, name) => {
if (!obj) return;
var part,
names = name.split(".");
const code_points = [[0, 65535]];
const accent_pat = '[\u0300-\u036F\u{b7}\u{2be}]'; // \u{2bc}
while ((part = names.shift()) && (obj = obj[part]));
/** @type {TUnicodeMap} */
return obj;
};
/**
* Calculates how close of a match the
* given value is against a search token.
*
*/
let unicode_map;
/** @type {RegExp} */
const scoreValue = (value, token, weight) => {
var score, pos;
if (!value) return 0;
value = value + '';
pos = value.search(token.regex);
if (pos === -1) return 0;
score = token.string.length / value.length;
if (pos === 0) score += 0.5;
return score * weight;
};
/**
*
* https://stackoverflow.com/questions/63006601/why-does-u-throw-an-invalid-escape-error
*/
let multi_char_reg;
const max_char_length = 3;
/** @type {TUnicodeMap} */
const escape_regex = str => {
return (str + '').replace(/([\$\(-\+\.\?\[-\^\{-\}])/g, '\\$1');
};
/**
* Cast object property to an array if it exists and has a value
*
*/
const latin_convert = {
'æ': 'ae',
'ⱥ': 'a',
'ø': 'o',
'⁄': '/',
'∕': '/'
};
const convert_pat = new RegExp(Object.keys(latin_convert).join('|') + '|' + accent_pat, 'gu');
/**
* Initialize the unicode_map from the give code point ranges
*
* @param {TCodePoints=} _code_points
*/
const propToArray = (obj, key) => {
var value = obj[key];
if (typeof value == 'function') return value;
const initialize = _code_points => {
if (unicode_map !== undefined) return;
unicode_map = generateMap(_code_points || code_points);
};
/**
* Helper method for normalize a string
* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/normalize
* @param {string} str
* @param {string} form
*/
if (value && !Array.isArray(value)) {
obj[key] = [value];
}
};
/**
* Iterates over arrays and hashes.
*
* ```
* iterate(this.items, function(item, id) {
* // invoked for each item
* });
* ```
*
*/
const iterate = (object, callback) => {
if (Array.isArray(object)) {
object.forEach(callback);
} else {
for (var key in object) {
if (object.hasOwnProperty(key)) {
callback(object[key], key);
}
}
}
};
const cmp = (a, b) => {
if (typeof a === 'number' && typeof b === 'number') {
return a > b ? 1 : a < b ? -1 : 0;
}
const normalize = (str, form = 'NFKD') => str.normalize(form);
/**
* Compatibility Decomposition without reordering string
* calling str.normalize('NFKD') on \u{594}\u{595}\u{596} becomes \u{596}\u{594}\u{595}
* @param {string} str
*/
a = asciifold(a + '').toLowerCase();
b = asciifold(b + '').toLowerCase();
if (a > b) return 1;
if (b > a) return -1;
return 0;
};
/**
* sifter.js
* Copyright (c) 2013–2020 Brian Reavis & contributors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this
* file except in compliance with the License. You may obtain a copy of the License at:
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
* ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*
* @author Brian Reavis <brian@thirdroute.com>
*/
const decompose = str => {
if (str.match(/[\u0f71-\u0f81]/)) {
return toArray(str).reduce(
/**
* @param {string} result
* @param {string} char
*/
(result, char) => {
return result + normalize(char);
}, '');
}
class Sifter {
// []|{};
return normalize(str);
};
/**
* Remove accents
* via https://github.com/krisk/Fuse/issues/133#issuecomment-318692703
* @param {string} str
* @return {string}
*/
/**
* Textually searches arrays and hashes of objects
* by property (or multiple properties). Designed
* specifically for autocomplete.
*
*/
constructor(items, settings) {
this.items = void 0;
this.settings = void 0;
this.items = items;
this.settings = settings || {
diacritics: true
};
}
/**
* Splits a search string into an array of individual
* regexps to be used to match results.
*
*/
tokenize(query, respect_word_boundaries, weights) {
if (!query || !query.length) return [];
const tokens = [];
const words = query.split(/\s+/);
var field_regex;
const asciifold = str => {
return decompose(str).toLowerCase().replace(convert_pat,
/** @type {string} */
char => {
return latin_convert[char] || '';
});
};
/**
* Generate a list of unicode variants from the list of code points
* @param {TCodePoints} code_points
* @yield {TCodePointObj}
*/
if (weights) {
field_regex = new RegExp('^(' + Object.keys(weights).map(escape_regex).join('|') + ')\:(.*)$');
}
words.forEach(word => {
let field_match;
let field = null;
let regex = null; // look for "field:query" tokens
function* generator(code_points) {
for (const [code_point_min, code_point_max] of code_points) {
for (let i = code_point_min; i <= code_point_max; i++) {
let composed = String.fromCharCode(i);
let folded = asciifold(composed);
if (field_regex && (field_match = word.match(field_regex))) {
field = field_match[1];
word = field_match[2];
}
if (folded == composed.toLowerCase()) {
continue;
} // skip when folded is a string longer than 3 characters long
// bc the resulting regex patterns will be long
// eg:
// folded صلى الله عليه وسلم length 18 code point 65018
// folded جل جلاله length 8 code point 65019
if (word.length > 0) {
if (this.settings.diacritics) {
regex = diacriticRegexPoints(word);
} else {
regex = escape_regex(word);
}
if (respect_word_boundaries) regex = "\\b" + regex;
}
if (folded.length > max_char_length) {
continue;
}
tokens.push({
string: word,
regex: regex ? new RegExp(regex, 'iu') : null,
field: field
});
});
return tokens;
}
if (folded.length == 0) {
continue;
}
/**
* Returns a function to be used to score individual results.
*
* Good matches will have a higher score than poor matches.
* If an item is not a match, 0 will be returned by the function.
*
* @returns {function}
*/
getScoreFunction(query, options) {
var search = this.prepareSearch(query, options);
return this._getScoreFunction(search);
}
let decomposed = normalize(composed);
let recomposed = normalize(decomposed, 'NFC');
_getScoreFunction(search) {
const tokens = search.tokens,
token_count = tokens.length;
if (recomposed === composed && folded === decomposed) {
continue;
}
if (!token_count) {
return function () {
return 0;
};
}
yield {
folded: folded,
composed: composed,
code_point: i
};
}
}
}
/**
* Generate a unicode map from the list of code points
* @param {TCodePoints} code_points
* @return {TUnicodeSets}
*/
const fields = search.options.fields,
weights = search.weights,
field_count = fields.length,
getAttrFn = search.getAttrFn;
if (!field_count) {
return function () {
return 1;
};
}
/**
* Calculates the score of an object
* against the search query.
*
*/
const generateSets = code_points => {
/** @type {{[key:string]:Set<string>}} */
const unicode_sets = {};
/**
* @param {string} folded
* @param {string} to_add
*/
const addMatching = (folded, to_add) => {
/** @type {Set<string>} */
const folded_set = unicode_sets[folded] || new Set();
const patt = new RegExp('^' + setToPattern(folded_set) + '$', 'iu');
const scoreObject = function () {
if (field_count === 1) {
return function (token, data) {
const field = fields[0].field;
return scoreValue(getAttrFn(data, field), token, weights[field]);
};
}
if (to_add.match(patt)) {
return;
}
return function (token, data) {
var sum = 0; // is the token specific to a field?
folded_set.add(escape_regex(to_add));
unicode_sets[folded] = folded_set;
};
if (token.field) {
const value = getAttrFn(data, token.field);
for (let value of generator(code_points)) {
addMatching(value.folded, value.folded);
addMatching(value.folded, value.composed);
}
if (!token.regex && value) {
sum += 1 / field_count;
} else {
sum += scoreValue(value, token, 1);
}
} else {
iterate(weights, (weight, field) => {
sum += scoreValue(getAttrFn(data, field), token, weight);
});
}
return unicode_sets;
};
/**
* Generate a unicode map from the list of code points
* ae => (?:(?:ae|Æ|Ǽ|Ǣ)|(?:A|Ⓐ|A...)(?:E|ɛ|Ⓔ...))
*
* @param {TCodePoints} code_points
* @return {TUnicodeMap}
*/
return sum / field_count;
};
}();
if (token_count === 1) {
return function (data) {
return scoreObject(tokens[0], data);
};
}
const generateMap = code_points => {
/** @type {TUnicodeSets} */
const unicode_sets = generateSets(code_points);
/** @type {TUnicodeMap} */
if (search.options.conjunction === 'and') {
return function (data) {
var i = 0,
score,
sum = 0;
const unicode_map = {};
/** @type {string[]} */
for (; i < token_count; i++) {
score = scoreObject(tokens[i], data);
if (score <= 0) return 0;
sum += score;
}
let multi_char = [];
return sum / token_count;
};
} else {
return function (data) {
var sum = 0;
iterate(tokens, token => {
sum += scoreObject(token, data);
});
return sum / token_count;
};
}
}
for (let folded in unicode_sets) {
let set = unicode_sets[folded];
/**
* Returns a function that can be used to compare two
* results, for sorting purposes. If no sorting should
* be performed, `null` will be returned.
*
* @return function(a,b)
*/
getSortFunction(query, options) {
var search = this.prepareSearch(query, options);
return this._getSortFunction(search);
}
if (set) {
unicode_map[folded] = setToPattern(set);
}
_getSortFunction(search) {
var i, n, implicit_score;
const self = this,
options = search.options,
sort = !search.query && options.sort_empty ? options.sort_empty : options.sort,
sort_flds = [],
multipliers = [];
if (folded.length > 1) {
multi_char.push(escape_regex(folded));
}
}
if (typeof sort == 'function') {
return sort.bind(this);
}
/**
* Fetches the specified sort field value
* from a search result item.
*
*/
multi_char.sort((a, b) => b.length - a.length);
const multi_char_patt = arrayToPattern(multi_char);
multi_char_reg = new RegExp('^' + multi_char_patt, 'u');
return unicode_map;
};
/**
* Map each element of an array from it's folded value to all possible unicode matches
* @param {string[]} strings
* @param {number} min_replacement
* @return {string}
*/
const get_field = function get_field(name, result) {
if (name === '$score') return result.score;
return search.getAttrFn(self.items[result.id], name);
}; // parse options
const mapSequence = (strings, min_replacement = 1) => {
let chars_replaced = 0;
strings = strings.map(str => {
if (unicode_map[str]) {
chars_replaced += str.length;
}
return unicode_map[str] || str;
});
if (sort) {
for (i = 0, n = sort.length; i < n; i++) {
if (search.query || sort[i].field !== '$score') {
sort_flds.push(sort[i]);
}
}
} // the "$score" field is implied to be the primary
// sort field, unless it's manually specified
if (chars_replaced >= min_replacement) {
return sequencePattern(strings);
}
return '';
};
/**
* Convert a short string and split it into all possible patterns
* Keep a pattern only if min_replacement is met
*
* 'abc'
* => [['abc'],['ab','c'],['a','bc'],['a','b','c']]
* => ['abc-pattern','ab-c-pattern'...]
*
*
* @param {string} str
* @param {number} min_replacement
* @return {string}
*/
if (search.query) {
implicit_score = true;
for (i = 0, n = sort_flds.length; i < n; i++) {
if (sort_flds[i].field === '$score') {
implicit_score = false;
break;
}
}
const substringsToPattern = (str, min_replacement = 1) => {
min_replacement = Math.max(min_replacement, str.length - 1);
return arrayToPattern(allSubstrings(str).map(sub_pat => {
return mapSequence(sub_pat, min_replacement);
}));
};
/**
* Convert an array of sequences into a pattern
* [{start:0,end:3,length:3,substr:'iii'}...] => (?:iii...)
*
* @param {Sequence[]} sequences
* @param {boolean} all
*/
if (implicit_score) {
sort_flds.unshift({
field: '$score',
direction: 'desc'
});
}
} else {
for (i = 0, n = sort_flds.length; i < n; i++) {
if (sort_flds[i].field === '$score') {
sort_flds.splice(i, 1);
break;
}
}
} // build function
const sequencesToPattern = (sequences, all = true) => {
let min_replacement = sequences.length > 1 ? 1 : 0;
return arrayToPattern(sequences.map(sequence => {
let seq = [];
const len = all ? sequence.length() : sequence.length() - 1;
const sort_flds_count = sort_flds.length;
for (let j = 0; j < len; j++) {
seq.push(substringsToPattern(sequence.substrs[j] || '', min_replacement));
}
if (!sort_flds_count) {
return null;
}
return sequencePattern(seq);
}));
};
/**
* Return true if the sequence is already in the sequences
* @param {Sequence} needle_seq
* @param {Sequence[]} sequences
*/
return function (a, b) {
var i, result, field;
for (i = 0; i < sort_flds_count; i++) {
field = sort_flds[i].field;
let multiplier = multipliers[i];
const inSequences = (needle_seq, sequences) => {
for (const seq of sequences) {
if (seq.start != needle_seq.start || seq.end != needle_seq.end) {
continue;
}
if (multiplier == undefined) {
multiplier = sort_flds[i].direction === 'desc' ? -1 : 1;
}
if (seq.substrs.join('') !== needle_seq.substrs.join('')) {
continue;
}
result = multiplier * cmp(get_field(field, a), get_field(field, b));
if (result) return result;
}
let needle_parts = needle_seq.parts;
/**
* @param {TSequencePart} part
*/
return 0;
};
}
const filter = part => {
for (const needle_part of needle_parts) {
if (needle_part.start === part.start && needle_part.substr === part.substr) {
return false;
}
/**
* Parses a search query and returns an object
* with tokens and fields ready to be populated
* with results.
*
*/
prepareSearch(query, optsUser) {
const weights = {};
var options = Object.assign({}, optsUser);
propToArray(options, 'sort');
propToArray(options, 'sort_empty'); // convert fields to new format
if (part.length == 1 || needle_part.length == 1) {
continue;
} // check for overlapping parts
// a = ['::=','==']
// b = ['::','===']
// a = ['r','sm']
// b = ['rs','m']
if (options.fields) {
propToArray(options, 'fields');
const fields = [];
options.fields.forEach(field => {
if (typeof field == 'string') {
field = {
field: field,
weight: 1
};
}
fields.push(field);
weights[field.field] = 'weight' in field ? field.weight : 1;
});
options.fields = fields;
}
if (part.start < needle_part.start && part.end > needle_part.start) {
return true;
}
return {
options: options,
query: query.toLowerCase().trim(),
tokens: this.tokenize(query, options.respect_word_boundaries, weights),
total: 0,
items: [],
weights: weights,
getAttrFn: options.nesting ? getAttrNesting : getAttr
};
}
if (needle_part.start < part.start && needle_part.end > part.start) {
return true;
}
}
/**
* Searches through all items and returns a sorted array of matches.
*
*/
search(query, options) {
var self = this,
score,
search;
search = this.prepareSearch(query, options);
options = search.options;
query = search.query; // generate result scoring function
return false;
};
const fn_score = options.score || self._getScoreFunction(search); // perform search and sort
let filtered = seq.parts.filter(filter);
if (filtered.length > 0) {
continue;
}
if (query.length) {
iterate(self.items, (item, id) => {
score = fn_score(item);
return true;
}
if (options.filter === false || score > 0) {
search.items.push({
'score': score,
'id': id
});
}
});
} else {
iterate(self.items, (_, id) => {
search.items.push({
'score': 1,
'id': id
});
});
}
return false;
};
const fn_sort = self._getSortFunction(search);
class Sequence {
constructor() {
/** @type {TSequencePart[]} */
this.parts = [];
/** @type {string[]} */
if (fn_sort) search.items.sort(fn_sort); // apply limits
this.substrs = [];
this.start = 0;
this.end = 0;
}
/**
* @param {TSequencePart|undefined} part
*/
search.total = search.items.length;
if (typeof options.limit === 'number') {
search.items = search.items.slice(0, options.limit);
}
add(part) {
if (part) {
this.parts.push(part);
this.substrs.push(part.substr);
this.start = Math.min(part.start, this.start);
this.end = Math.max(part.end, this.end);
}
}
return search;
}
last() {
return this.parts[this.parts.length - 1];
}
}
length() {
return this.parts.length;
}
/**
* @param {number} position
* @param {TSequencePart} last_piece
*/
return Sifter;
clone(position, last_piece) {
let clone = new Sequence();
let parts = JSON.parse(JSON.stringify(this.parts));
let last_part = parts.pop();
for (const part of parts) {
clone.add(part);
}
let last_substr = last_piece.substr.substring(0, position - last_part.start);
let clone_last_len = last_substr.length;
clone.add({
start: last_part.start,
end: last_part.start + clone_last_len,
length: clone_last_len,
substr: last_substr
});
return clone;
}
}
/**
* Expand a regular expression pattern to include unicode variants
* eg /a/ becomes /aⓐaẚàáâầấẫẩãāăằắẵẳȧǡäǟảåǻǎȁȃạậặḁąⱥɐɑAⒶAÀÁÂẦẤẪẨÃĀĂẰẮẴẲȦǠÄǞẢÅǺǍȀȂẠẬẶḀĄȺⱯ/
*
* Issue:
* ﺊﺋ [ 'ﺊ = \\u{fe8a}', 'ﺋ = \\u{fe8b}' ]
* becomes: ئئ [ 'ي = \\u{64a}', 'ٔ = \\u{654}', 'ي = \\u{64a}', 'ٔ = \\u{654}' ]
*
* İIJ = IIJ = ⅡJ
*
* 1/2/4
*
* @param {string} str
* @return {string|undefined}
*/
const getPattern = str => {
initialize();
str = asciifold(str);
let pattern = '';
let sequences = [new Sequence()];
for (let i = 0; i < str.length; i++) {
let substr = str.substring(i);
let match = substr.match(multi_char_reg);
const char = str.substring(i, i + 1);
const match_str = match ? match[0] : null; // loop through sequences
// add either the char or multi_match
let overlapping = [];
let added_types = new Set();
for (const sequence of sequences) {
const last_piece = sequence.last();
if (!last_piece || last_piece.length == 1 || last_piece.end <= i) {
// if we have a multi match
if (match_str) {
const len = match_str.length;
sequence.add({
start: i,
end: i + len,
length: len,
substr: match_str
});
added_types.add('1');
} else {
sequence.add({
start: i,
end: i + 1,
length: 1,
substr: char
});
added_types.add('2');
}
} else if (match_str) {
let clone = sequence.clone(i, last_piece);
const len = match_str.length;
clone.add({
start: i,
end: i + len,
length: len,
substr: match_str
});
overlapping.push(clone);
} else {
// don't add char
// adding would create invalid patterns: 234 => [2,34,4]
added_types.add('3');
}
} // if we have overlapping
if (overlapping.length > 0) {
// ['ii','iii'] before ['i','i','iii']
overlapping = overlapping.sort((a, b) => {
return a.length() - b.length();
});
for (let clone of overlapping) {
// don't add if we already have an equivalent sequence
if (inSequences(clone, sequences)) {
continue;
}
sequences.push(clone);
}
continue;
} // if we haven't done anything unique
// clean up the patterns
// helps keep patterns smaller
// if str = 'r₨㎧aarss', pattern will be 446 instead of 655
if (i > 0 && added_types.size == 1 && !added_types.has('3')) {
pattern += sequencesToPattern(sequences, false);
let new_seq = new Sequence();
const old_seq = sequences[0];
if (old_seq) {
new_seq.add(old_seq.last());
}
sequences = [new_seq];
}
}
pattern += sequencesToPattern(sequences, true);
return pattern;
};
/**
* A property getter resolving dot-notation
* @param {Object} obj The root object to fetch property on
* @param {String} name The optionally dotted property name to fetch
* @return {Object} The resolved property value
*/
const getAttr = (obj, name) => {
if (!obj) return;
return obj[name];
};
/**
* A property getter resolving dot-notation
* @param {Object} obj The root object to fetch property on
* @param {String} name The optionally dotted property name to fetch
* @return {Object} The resolved property value
*/
const getAttrNesting = (obj, name) => {
if (!obj) return;
var part,
names = name.split(".");
while ((part = names.shift()) && (obj = obj[part]));
return obj;
};
/**
* Calculates how close of a match the
* given value is against a search token.
*
*/
const scoreValue = (value, token, weight) => {
var score, pos;
if (!value) return 0;
value = value + '';
if (token.regex == null) return 0;
pos = value.search(token.regex);
if (pos === -1) return 0;
score = token.string.length / value.length;
if (pos === 0) score += 0.5;
return score * weight;
};
/**
* Cast object property to an array if it exists and has a value
*
*/
const propToArray = (obj, key) => {
var value = obj[key];
if (typeof value == 'function') return value;
if (value && !Array.isArray(value)) {
obj[key] = [value];
}
};
/**
* Iterates over arrays and hashes.
*
* ```
* iterate(this.items, function(item, id) {
* // invoked for each item
* });
* ```
*
*/
const iterate = (object, callback) => {
if (Array.isArray(object)) {
object.forEach(callback);
} else {
for (var key in object) {
if (object.hasOwnProperty(key)) {
callback(object[key], key);
}
}
}
};
const cmp = (a, b) => {
if (typeof a === 'number' && typeof b === 'number') {
return a > b ? 1 : a < b ? -1 : 0;
}
a = asciifold(a + '').toLowerCase();
b = asciifold(b + '').toLowerCase();
if (a > b) return 1;
if (b > a) return -1;
return 0;
};
/**
* sifter.js
* Copyright (c) 2013–2020 Brian Reavis & contributors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this
* file except in compliance with the License. You may obtain a copy of the License at:
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
* ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*
* @author Brian Reavis <brian@thirdroute.com>
*/
class Sifter {
// []|{};
/**
* Textually searches arrays and hashes of objects
* by property (or multiple properties). Designed
* specifically for autocomplete.
*
*/
constructor(items, settings) {
this.items = void 0;
this.settings = void 0;
this.items = items;
this.settings = settings || {
diacritics: true
};
}
/**
* Splits a search string into an array of individual
* regexps to be used to match results.
*
*/
tokenize(query, respect_word_boundaries, weights) {
if (!query || !query.length) return [];
const tokens = [];
const words = query.split(/\s+/);
var field_regex;
if (weights) {
field_regex = new RegExp('^(' + Object.keys(weights).map(escape_regex).join('|') + ')\:(.*)$');
}
words.forEach(word => {
let field_match;
let field = null;
let regex = null; // look for "field:query" tokens
if (field_regex && (field_match = word.match(field_regex))) {
field = field_match[1];
word = field_match[2];
}
if (word.length > 0) {
if (this.settings.diacritics) {
regex = getPattern(word) || null;
} else {
regex = escape_regex(word);
}
if (regex && respect_word_boundaries) regex = "\\b" + regex;
}
tokens.push({
string: word,
regex: regex ? new RegExp(regex, 'iu') : null,
field: field
});
});
return tokens;
}
/**
* Returns a function to be used to score individual results.
*
* Good matches will have a higher score than poor matches.
* If an item is not a match, 0 will be returned by the function.
*
* @returns {T.ScoreFn}
*/
getScoreFunction(query, options) {
var search = this.prepareSearch(query, options);
return this._getScoreFunction(search);
}
/**
* @returns {T.ScoreFn}
*
*/
_getScoreFunction(search) {
const tokens = search.tokens,
token_count = tokens.length;
if (!token_count) {
return function () {
return 0;
};
}
const fields = search.options.fields,
weights = search.weights,
field_count = fields.length,
getAttrFn = search.getAttrFn;
if (!field_count) {
return function () {
return 1;
};
}
/**
* Calculates the score of an object
* against the search query.
*
*/
const scoreObject = function () {
if (field_count === 1) {
return function (token, data) {
const field = fields[0].field;
return scoreValue(getAttrFn(data, field), token, weights[field] || 1);
};
}
return function (token, data) {
var sum = 0; // is the token specific to a field?
if (token.field) {
const value = getAttrFn(data, token.field);
if (!token.regex && value) {
sum += 1 / field_count;
} else {
sum += scoreValue(value, token, 1);
}
} else {
iterate(weights, (weight, field) => {
sum += scoreValue(getAttrFn(data, field), token, weight);
});
}
return sum / field_count;
};
}();
if (token_count === 1) {
return function (data) {
return scoreObject(tokens[0], data);
};
}
if (search.options.conjunction === 'and') {
return function (data) {
var score,
sum = 0;
for (let token of tokens) {
score = scoreObject(token, data);
if (score <= 0) return 0;
sum += score;
}
return sum / token_count;
};
} else {
return function (data) {
var sum = 0;
iterate(tokens, token => {
sum += scoreObject(token, data);
});
return sum / token_count;
};
}
}
/**
* Returns a function that can be used to compare two
* results, for sorting purposes. If no sorting should
* be performed, `null` will be returned.
*
* @return function(a,b)
*/
getSortFunction(query, options) {
var search = this.prepareSearch(query, options);
return this._getSortFunction(search);
}
_getSortFunction(search) {
var implicit_score,
sort_flds = [];
const self = this,
options = search.options,
sort = !search.query && options.sort_empty ? options.sort_empty : options.sort;
if (typeof sort == 'function') {
return sort.bind(this);
}
/**
* Fetches the specified sort field value
* from a search result item.
*
*/
const get_field = function get_field(name, result) {
if (name === '$score') return result.score;
return search.getAttrFn(self.items[result.id], name);
}; // parse options
if (sort) {
for (let s of sort) {
if (search.query || s.field !== '$score') {
sort_flds.push(s);
}
}
} // the "$score" field is implied to be the primary
// sort field, unless it's manually specified
if (search.query) {
implicit_score = true;
for (let fld of sort_flds) {
if (fld.field === '$score') {
implicit_score = false;
break;
}
}
if (implicit_score) {
sort_flds.unshift({
field: '$score',
direction: 'desc'
});
} // without a search.query, all items will have the same score
} else {
sort_flds = sort_flds.filter(fld => fld.field !== '$score');
} // build function
const sort_flds_count = sort_flds.length;
if (!sort_flds_count) {
return null;
}
return function (a, b) {
var result, field;
for (let sort_fld of sort_flds) {
field = sort_fld.field;
let multiplier = sort_fld.direction === 'desc' ? -1 : 1;
result = multiplier * cmp(get_field(field, a), get_field(field, b));
if (result) return result;
}
return 0;
};
}
/**
* Parses a search query and returns an object
* with tokens and fields ready to be populated
* with results.
*
*/
prepareSearch(query, optsUser) {
const weights = {};
var options = Object.assign({}, optsUser);
propToArray(options, 'sort');
propToArray(options, 'sort_empty'); // convert fields to new format
if (options.fields) {
propToArray(options, 'fields');
const fields = [];
options.fields.forEach(field => {
if (typeof field == 'string') {
field = {
field: field,
weight: 1
};
}
fields.push(field);
weights[field.field] = 'weight' in field ? field.weight : 1;
});
options.fields = fields;
}
return {
options: options,
query: query.toLowerCase().trim(),
tokens: this.tokenize(query, options.respect_word_boundaries, weights),
total: 0,
items: [],
weights: weights,
getAttrFn: options.nesting ? getAttrNesting : getAttr
};
}
/**
* Searches through all items and returns a sorted array of matches.
*
*/
search(query, options) {
var self = this,
score,
search;
search = this.prepareSearch(query, options);
options = search.options;
query = search.query; // generate result scoring function
const fn_score = options.score || self._getScoreFunction(search); // perform search and sort
if (query.length) {
iterate(self.items, (item, id) => {
score = fn_score(item);
if (options.filter === false || score > 0) {
search.items.push({
'score': score,
'id': id
});
}
});
} else {
iterate(self.items, (_, id) => {
search.items.push({
'score': 1,
'id': id
});
});
}
const fn_sort = self._getSortFunction(search);
if (fn_sort) search.items.sort(fn_sort); // apply limits
search.total = search.items.length;
if (typeof options.limit === 'number') {
search.items = search.items.slice(0, options.limit);
}
return search;
}
}
exports.Sifter = Sifter;
exports.cmp = cmp;
exports.getAttr = getAttr;
exports.getAttrNesting = getAttrNesting;
exports.getPattern = getPattern;
exports.iterate = iterate;
exports.propToArray = propToArray;
exports.scoreValue = scoreValue;
Object.defineProperty(exports, '__esModule', { value: true });
})));
//# sourceMappingURL=sifter.js.map

@@ -1,82 +0,114 @@

!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e="undefined"!=typeof globalThis?globalThis:e||self).sifter=t()}(this,(function(){"use strict"
var e
const t="[̀-ͯ·ʾ]",r=new RegExp(t,"gu")
var n
const i={"æ":"ae","ⱥ":"a","ø":"o"},o=new RegExp(Object.keys(i).join("|"),"gu"),s=[[0,65535]],c=e=>e.normalize("NFKD").replace(r,"").toLowerCase().replace(o,(function(e){return i[e]||e})),u=(e,t="|")=>{if(1===e.length&&null!=e[0])return e[0]
var r=1
return e.forEach((e=>{r=Math.max(r,e.length)})),1==r?"["+e.join("")+"]":"(?:"+e.join(t)+")"},f=e=>{const t=e.map((e=>d(e)))
return u(t)},a=e=>{if(1===e.length)return[[e]]
var t=[]
return a(e.substring(1)).forEach((function(r){var n=r.slice(0)
n[0]=e.charAt(0)+n[0],t.push(n),(n=r.slice(0)).unshift(e.charAt(0)),t.push(n)})),t},l=t=>{void 0===n&&(n=(t=>{var r={}
t.forEach((e=>{for(let t=e[0];t<=e[1];t++){let e=String.fromCharCode(t),n=c(e)
if(n==e.toLowerCase())continue
if(n.length>3)continue
const i=r[n]||[n],o=new RegExp(f(i),"iu")
e.match(o)||(i.push(e),r[n]=i)}})),Object.keys(r).forEach((e=>{(r[e]||[]).length<2&&delete r[e]}))
let n=Object.keys(r).sort(((e,t)=>t.length-e.length))
e=new RegExp("("+f(n)+"[̀-ͯ·ʾ]*)","gu")
var i={}
return n.sort(((e,t)=>e.length-t.length)).forEach((e=>{var t=a(e).map((e=>(e=e.map((e=>r.hasOwnProperty(e)?f(r[e]):e)),u(e,""))))
i[e]=u(t)})),i})(s))
return t.normalize("NFKD").toLowerCase().split(e).map((e=>{const t=c(e)
return""==t?"":n.hasOwnProperty(t)?n[t]:e})).join("")},h=(e,t)=>{if(e)return e[t]},g=(e,t)=>{if(e){for(var r,n=t.split(".");(r=n.shift())&&(e=e[r]););return e}},p=(e,t,r)=>{var n,i
return e?-1===(i=(e+="").search(t.regex))?0:(n=t.string.length/e.length,0===i&&(n+=.5),n*r):0},d=e=>(e+"").replace(/([\$\(-\+\.\?\[-\^\{-\}])/g,"\\$1"),m=(e,t)=>{var r=e[t]
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self).sifter={})}(this,(function(t){"use strict"
const e=t=>(t=t.filter(Boolean)).length<2?t[0]||"":1==o(t)?"["+t.join("")+"]":"(?:"+t.join("|")+")",r=t=>{if(!s(t))return t.join("")
let e="",r=0
const n=()=>{r>1&&(e+="{"+r+"}")}
return t.forEach(((s,i)=>{s!==t[i-1]?(n(),e+=s,r=1):r++})),n(),e},n=t=>{let r=u(t)
return e(r)},s=t=>new Set(t).size!==t.length,i=t=>(t+"").replace(/([\$\(-\+\.\?\[-\^\{-\}])/g,"\\$1"),o=t=>t.reduce(((t,e)=>Math.max(t,l(e))),0),l=t=>u(t).length,u=t=>Array.from(t),f=t=>{if(1===t.length)return[[t]]
let e=[]
const r=t.substring(1)
return f(r).forEach((function(r){let n=r.slice(0)
n[0]=t.charAt(0)+n[0],e.push(n),n=r.slice(0),n.unshift(t.charAt(0)),e.push(n)})),e},c=[[0,65535]]
let a,h
const d={"æ":"ae","ⱥ":"a","ø":"o","⁄":"/","∕":"/"},g=new RegExp(Object.keys(d).join("|")+"|[̀-ͯ·ʾ]","gu"),p=(t,e="NFKD")=>t.normalize(e),m=t=>(t=>t.match(/[\u0f71-\u0f81]/)?u(t).reduce(((t,e)=>t+p(e)),""):p(t))(t).toLowerCase().replace(g,(t=>d[t]||""))
const b=t=>{const e={},r=(t,r)=>{const s=e[t]||new Set,o=new RegExp("^"+n(s)+"$","iu")
r.match(o)||(s.add(i(r)),e[t]=s)}
for(let e of function*(t){for(const[e,r]of t)for(let t=e;t<=r;t++){let e=String.fromCharCode(t),r=m(e)
if(r==e.toLowerCase())continue
if(r.length>3)continue
if(0==r.length)continue
let n=p(e)
p(n,"NFC")===e&&r===n||(yield{folded:r,composed:e,code_point:t})}}(t))r(e.folded,e.folded),r(e.folded,e.composed)
return e},y=t=>{const r=b(t),s={}
let o=[]
for(let t in r){let e=r[t]
e&&(s[t]=n(e)),t.length>1&&o.push(i(t))}o.sort(((t,e)=>e.length-t.length))
const l=e(o)
return h=new RegExp("^"+l,"u"),s},w=(t,n=1)=>(n=Math.max(n,t.length-1),e(f(t).map((t=>((t,e=1)=>{let n=0
return t=t.map((t=>(a[t]&&(n+=t.length),a[t]||t))),n>=e?r(t):""})(t,n))))),v=(t,n=!0)=>{let s=t.length>1?1:0
return e(t.map((t=>{let e=[]
const i=n?t.length():t.length()-1
for(let r=0;r<i;r++)e.push(w(t.substrs[r]||"",s))
return r(e)})))},S=(t,e)=>{for(const r of e){if(r.start!=t.start||r.end!=t.end)continue
if(r.substrs.join("")!==t.substrs.join(""))continue
let e=t.parts
const n=t=>{for(const r of e){if(r.start===t.start&&r.substr===t.substr)return!1
if(1!=t.length&&1!=r.length){if(t.start<r.start&&t.end>r.start)return!0
if(r.start<t.start&&r.end>t.start)return!0}}return!1}
if(!(r.parts.filter(n).length>0))return!0}return!1}
class x{constructor(){this.parts=[],this.substrs=[],this.start=0,this.end=0}add(t){t&&(this.parts.push(t),this.substrs.push(t.substr),this.start=Math.min(t.start,this.start),this.end=Math.max(t.end,this.end))}last(){return this.parts[this.parts.length-1]}length(){return this.parts.length}clone(t,e){let r=new x,n=JSON.parse(JSON.stringify(this.parts)),s=n.pop()
for(const t of n)r.add(t)
let i=e.substr.substring(0,t-s.start),o=i.length
return r.add({start:s.start,end:s.start+o,length:o,substr:i}),r}}const _=t=>{var e
void 0===a&&(a=y(e||c)),t=m(t)
let r="",n=[new x]
for(let e=0;e<t.length;e++){let s=t.substring(e).match(h)
const i=t.substring(e,e+1),o=s?s[0]:null
let l=[],u=new Set
for(const t of n){const r=t.last()
if(!r||1==r.length||r.end<=e)if(o){const r=o.length
t.add({start:e,end:e+r,length:r,substr:o}),u.add("1")}else t.add({start:e,end:e+1,length:1,substr:i}),u.add("2")
else if(o){let n=t.clone(e,r)
const s=o.length
n.add({start:e,end:e+s,length:s,substr:o}),l.push(n)}else u.add("3")}if(l.length>0){l=l.sort(((t,e)=>t.length()-e.length()))
for(let t of l)S(t,n)||n.push(t)}else if(e>0&&1==u.size&&!u.has("3")){r+=v(n,!1)
let t=new x
const e=n[0]
e&&t.add(e.last()),n=[t]}}return r+=v(n,!0),r},j=(t,e)=>{if(t)return t[e]},A=(t,e)=>{if(t){for(var r,n=e.split(".");(r=n.shift())&&(t=t[r]););return t}},F=(t,e,r)=>{var n,s
return t?(t+="",null==e.regex||-1===(s=t.search(e.regex))?0:(n=e.string.length/t.length,0===s&&(n+=.5),n*r)):0},E=(t,e)=>{var r=t[e]
if("function"==typeof r)return r
r&&!Array.isArray(r)&&(e[t]=[r])},y=(e,t)=>{if(Array.isArray(e))e.forEach(t)
else for(var r in e)e.hasOwnProperty(r)&&t(e[r],r)},v=(e,t)=>"number"==typeof e&&"number"==typeof t?e>t?1:e<t?-1:0:(e=c(e+"").toLowerCase())>(t=c(t+"").toLowerCase())?1:t>e?-1:0
;/**
* sifter.js
* Copyright (c) 2013–2020 Brian Reavis & contributors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this
* file except in compliance with the License. You may obtain a copy of the License at:
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
* ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*
* @author Brian Reavis <brian@thirdroute.com>
*/
return class{constructor(e,t){this.items=void 0,this.settings=void 0,this.items=e,this.settings=t||{diacritics:!0}}tokenize(e,t,r){if(!e||!e.length)return[]
const n=[],i=e.split(/\s+/)
r&&!Array.isArray(r)&&(t[e]=[r])},$=(t,e)=>{if(Array.isArray(t))t.forEach(e)
else for(var r in t)t.hasOwnProperty(r)&&e(t[r],r)},C=(t,e)=>"number"==typeof t&&"number"==typeof e?t>e?1:t<e?-1:0:(t=m(t+"").toLowerCase())>(e=m(e+"").toLowerCase())?1:e>t?-1:0
t.Sifter=
/**
* sifter.js
* Copyright (c) 2013–2020 Brian Reavis & contributors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this
* file except in compliance with the License. You may obtain a copy of the License at:
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
* ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*
* @author Brian Reavis <brian@thirdroute.com>
*/
class{constructor(t,e){this.items=void 0,this.settings=void 0,this.items=t,this.settings=e||{diacritics:!0}}tokenize(t,e,r){if(!t||!t.length)return[]
const n=[],s=t.split(/\s+/)
var o
return r&&(o=new RegExp("^("+Object.keys(r).map(d).join("|")+"):(.*)$")),i.forEach((e=>{let r,i=null,s=null
o&&(r=e.match(o))&&(i=r[1],e=r[2]),e.length>0&&(s=this.settings.diacritics?l(e):d(e),t&&(s="\\b"+s)),n.push({string:e,regex:s?new RegExp(s,"iu"):null,field:i})})),n}getScoreFunction(e,t){var r=this.prepareSearch(e,t)
return this._getScoreFunction(r)}_getScoreFunction(e){const t=e.tokens,r=t.length
return r&&(o=new RegExp("^("+Object.keys(r).map(i).join("|")+"):(.*)$")),s.forEach((t=>{let r,s=null,l=null
o&&(r=t.match(o))&&(s=r[1],t=r[2]),t.length>0&&(l=this.settings.diacritics?_(t)||null:i(t),l&&e&&(l="\\b"+l)),n.push({string:t,regex:l?new RegExp(l,"iu"):null,field:s})})),n}getScoreFunction(t,e){var r=this.prepareSearch(t,e)
return this._getScoreFunction(r)}_getScoreFunction(t){const e=t.tokens,r=e.length
if(!r)return function(){return 0}
const n=e.options.fields,i=e.weights,o=n.length,s=e.getAttrFn
if(!o)return function(){return 1}
const c=1===o?function(e,t){const r=n[0].field
return p(s(t,r),e,i[r])}:function(e,t){var r=0
if(e.field){const n=s(t,e.field)
!e.regex&&n?r+=1/o:r+=p(n,e,1)}else y(i,((n,i)=>{r+=p(s(t,i),e,n)}))
return r/o}
return 1===r?function(e){return c(t[0],e)}:"and"===e.options.conjunction?function(e){for(var n,i=0,o=0;i<r;i++){if((n=c(t[i],e))<=0)return 0
o+=n}return o/r}:function(e){var n=0
return y(t,(t=>{n+=c(t,e)})),n/r}}getSortFunction(e,t){var r=this.prepareSearch(e,t)
return this._getSortFunction(r)}_getSortFunction(e){var t,r,n
const i=this,o=e.options,s=!e.query&&o.sort_empty?o.sort_empty:o.sort,c=[],u=[]
if("function"==typeof s)return s.bind(this)
const f=function(t,r){return"$score"===t?r.score:e.getAttrFn(i.items[r.id],t)}
if(s)for(t=0,r=s.length;t<r;t++)(e.query||"$score"!==s[t].field)&&c.push(s[t])
if(e.query){for(n=!0,t=0,r=c.length;t<r;t++)if("$score"===c[t].field){n=!1
break}n&&c.unshift({field:"$score",direction:"desc"})}else for(t=0,r=c.length;t<r;t++)if("$score"===c[t].field){c.splice(t,1)
break}const a=c.length
return a?function(e,t){var r,n,i
for(r=0;r<a;r++){i=c[r].field
let o=u[r]
if(null==o&&(o="desc"===c[r].direction?-1:1),n=o*v(f(i,e),f(i,t)))return n}return 0}:null}prepareSearch(e,t){const r={}
var n=Object.assign({},t)
if(m(n,"sort"),m(n,"sort_empty"),n.fields){m(n,"fields")
const e=[]
n.fields.forEach((t=>{"string"==typeof t&&(t={field:t,weight:1}),e.push(t),r[t.field]="weight"in t?t.weight:1})),n.fields=e}return{options:n,query:e.toLowerCase().trim(),tokens:this.tokenize(e,n.respect_word_boundaries,r),total:0,items:[],weights:r,getAttrFn:n.nesting?g:h}}search(e,t){var r,n,i=this
n=this.prepareSearch(e,t),t=n.options,e=n.query
const o=t.score||i._getScoreFunction(n)
e.length?y(i.items,((e,i)=>{r=o(e),(!1===t.filter||r>0)&&n.items.push({score:r,id:i})})):y(i.items,((e,t)=>{n.items.push({score:1,id:t})}))
const s=i._getSortFunction(n)
return s&&n.items.sort(s),n.total=n.items.length,"number"==typeof t.limit&&(n.items=n.items.slice(0,t.limit)),n}}}))
const n=t.options.fields,s=t.weights,i=n.length,o=t.getAttrFn
if(!i)return function(){return 1}
const l=1===i?function(t,e){const r=n[0].field
return F(o(e,r),t,s[r]||1)}:function(t,e){var r=0
if(t.field){const n=o(e,t.field)
!t.regex&&n?r+=1/i:r+=F(n,t,1)}else $(s,((n,s)=>{r+=F(o(e,s),t,n)}))
return r/i}
return 1===r?function(t){return l(e[0],t)}:"and"===t.options.conjunction?function(t){var n,s=0
for(let r of e){if((n=l(r,t))<=0)return 0
s+=n}return s/r}:function(t){var n=0
return $(e,(e=>{n+=l(e,t)})),n/r}}getSortFunction(t,e){var r=this.prepareSearch(t,e)
return this._getSortFunction(r)}_getSortFunction(t){var e,r=[]
const n=this,s=t.options,i=!t.query&&s.sort_empty?s.sort_empty:s.sort
if("function"==typeof i)return i.bind(this)
const o=function(e,r){return"$score"===e?r.score:t.getAttrFn(n.items[r.id],e)}
if(i)for(let e of i)(t.query||"$score"!==e.field)&&r.push(e)
if(t.query){e=!0
for(let t of r)if("$score"===t.field){e=!1
break}e&&r.unshift({field:"$score",direction:"desc"})}else r=r.filter((t=>"$score"!==t.field))
return r.length?function(t,e){var n,s
for(let i of r){if(s=i.field,n=("desc"===i.direction?-1:1)*C(o(s,t),o(s,e)))return n}return 0}:null}prepareSearch(t,e){const r={}
var n=Object.assign({},e)
if(E(n,"sort"),E(n,"sort_empty"),n.fields){E(n,"fields")
const t=[]
n.fields.forEach((e=>{"string"==typeof e&&(e={field:e,weight:1}),t.push(e),r[e.field]="weight"in e?e.weight:1})),n.fields=t}return{options:n,query:t.toLowerCase().trim(),tokens:this.tokenize(t,n.respect_word_boundaries,r),total:0,items:[],weights:r,getAttrFn:n.nesting?A:j}}search(t,e){var r,n,s=this
n=this.prepareSearch(t,e),e=n.options,t=n.query
const i=e.score||s._getScoreFunction(n)
t.length?$(s.items,((t,s)=>{r=i(t),(!1===e.filter||r>0)&&n.items.push({score:r,id:s})})):$(s.items,((t,e)=>{n.items.push({score:1,id:e})}))
const o=s._getSortFunction(n)
return o&&n.items.sort(o),n.total=n.items.length,"number"==typeof e.limit&&(n.items=n.items.slice(0,e.limit)),n}},t.cmp=C,t.getAttr=j,t.getAttrNesting=A,t.getPattern=_,t.iterate=$,t.propToArray=E,t.scoreValue=F,Object.defineProperty(t,"__esModule",{value:!0})}))
//# sourceMappingURL=sifter.min.js.map

@@ -17,12 +17,9 @@ /**

// @ts-ignore TS2691 "An import path cannot end with a '.ts' extension"
import { scoreValue, getAttr, getAttrNesting, escape_regex, propToArray, iterate, cmp } from './utils.ts';
// @ts-ignore TS2691 "An import path cannot end with a '.ts' extension"
import { diacriticRegexPoints } from './diacritics.ts';
// @ts-ignore TS2691 "An import path cannot end with a '.ts' extension"
import * as T from 'types.ts';
import { scoreValue, getAttr, getAttrNesting, propToArray, iterate, cmp } from './utils';
import { getPattern, escape_regex } from '@orchidjs/unicode-variants';
import * as T from './types';
export default class Sifter{
class Sifter{
public items; // []|{};
public items: any; // []|{};
public settings: T.Settings;

@@ -70,7 +67,7 @@

if( this.settings.diacritics ){
regex = diacriticRegexPoints(word);
regex = getPattern(word) || null;
}else{
regex = escape_regex(word);
}
if( respect_word_boundaries ) regex = "\\b"+regex;
if( regex && respect_word_boundaries ) regex = "\\b"+regex;
}

@@ -95,3 +92,3 @@

*
* @returns {function}
* @returns {T.ScoreFn}
*/

@@ -103,2 +100,6 @@ getScoreFunction(query:string, options:T.Options ){

/**
* @returns {T.ScoreFn}
*
*/
_getScoreFunction(search:T.PrepareObj ){

@@ -132,4 +133,4 @@ const tokens = search.tokens,

return function(token:T.Token, data:{}) {
const field = fields[0].field;
return scoreValue(getAttrFn(data, field), token, weights[field]);
const field = fields[0]!.field;
return scoreValue(getAttrFn(data, field), token, weights[field]||1);
};

@@ -166,3 +167,3 @@ }

return function(data:{}) {
return scoreObject(tokens[0], data);
return scoreObject(tokens[0]!, data);
};

@@ -173,5 +174,5 @@ }

return function(data:{}) {
var i = 0, score, sum = 0;
for (; i < token_count; i++) {
score = scoreObject(tokens[i], data);
var score, sum = 0;
for( let token of tokens){
score = scoreObject(token, data);
if (score <= 0) return 0;

@@ -200,3 +201,3 @@ sum += score;

*/
getSortFunction(query:string, options:T.Options) {
getSortFunction(query:string, options:Partial<T.Options>) {
var search = this.prepareSearch(query, options);

@@ -207,9 +208,8 @@ return this._getSortFunction(search);

_getSortFunction(search:T.PrepareObj){
var i, n, implicit_score;
var implicit_score,
sort_flds:T.Sort[] = [];
const self = this,
options = search.options,
sort = (!search.query && options.sort_empty) ? options.sort_empty : options.sort,
sort_flds:T.Sort[] = [],
multipliers:number[] = [];
sort = (!search.query && options.sort_empty) ? options.sort_empty : options.sort;

@@ -233,5 +233,5 @@

if (sort) {
for (i = 0, n = sort.length; i < n; i++) {
if (search.query || sort[i].field !== '$score') {
sort_flds.push(sort[i]);
for( let s of sort ){
if (search.query || s.field !== '$score') {
sort_flds.push(s);
}

@@ -245,4 +245,4 @@ }

implicit_score = true;
for (i = 0, n = sort_flds.length; i < n; i++) {
if (sort_flds[i].field === '$score') {
for( let fld of sort_flds ){
if( fld.field === '$score' ){
implicit_score = false;

@@ -255,9 +255,6 @@ break;

}
// without a search.query, all items will have the same score
} else {
for (i = 0, n = sort_flds.length; i < n; i++) {
if (sort_flds[i].field === '$score') {
sort_flds.splice(i, 1);
break;
}
}
sort_flds = sort_flds.filter((fld) => fld.field !== '$score' );
}

@@ -273,10 +270,7 @@

return function(a:T.ResultItem, b:T.ResultItem) {
var i, result, field;
for (i = 0; i < sort_flds_count; i++) {
field = sort_flds[i].field;
var result, field;
for( let sort_fld of sort_flds ){
field = sort_fld.field;
let multiplier = multipliers[i];
if( multiplier == undefined ){
multiplier = sort_flds[i].direction === 'desc' ? -1 : 1;
}
let multiplier = sort_fld.direction === 'desc' ? -1 : 1;

@@ -291,3 +285,3 @@ result = multiplier * cmp(

};
};

@@ -303,3 +297,3 @@

const weights:T.Weights = {};
var options = Object.assign({},optsUser);
var options = Object.assign({},optsUser) as T.Options;

@@ -325,3 +319,3 @@ propToArray(options,'sort');

return {
options : options,
options : options as T.Options,
query : query.toLowerCase().trim(),

@@ -341,3 +335,3 @@ tokens : this.tokenize(query, options.respect_word_boundaries, weights),

search(query:string, options:T.Options) : T.PrepareObj {
var self = this, score, search:T.PrepareObj;
var self = this, score, search: T.PrepareObj;

@@ -349,3 +343,3 @@ search = this.prepareSearch(query, options);

// generate result scoring function
const fn_score = options.score || self._getScoreFunction(search);
const fn_score:T.ScoreFn = options.score || self._getScoreFunction(search);

@@ -378,1 +372,3 @@ // perform search and sort

}
export { Sifter, scoreValue, getAttr, getAttrNesting, propToArray, iterate, cmp, getPattern }

@@ -1,4 +0,4 @@

// @ts-ignore TS2691 "An import path cannot end with a '.ts' extension"
import Sifter from 'sifter.ts';
import {Sifter} from './sifter';
export type Field = {

@@ -14,14 +14,15 @@ field: string,

export type SortFn = (this:Sifter, a:ResultItem, b:ResultItem)=>number;
export type SortFn = (this:Sifter, a:ResultItem, b:ResultItem)=>number;
export type Options = {
fields: Field[],
score: ()=>any,
filter: boolean,
limit: number,
conjunction: string,
sort: SortFn|Sort[],
sort_empty: SortFn|Sort[],
nesting: boolean,
respect_word_boundaries: boolean,
conjunction: string,
nesting: boolean,
score?: ScoreFn,
filter?: boolean,
sort_empty?: SortFn|Sort[],
respect_word_boundaries?: boolean,
limit?: number,
}

@@ -56,1 +57,4 @@

}
export type ScoreFn = (item:ResultItem) => number;
// @ts-ignore TS2691 "An import path cannot end with a '.ts' extension"
import { asciifold } from './diacritics.ts';
import { asciifold } from '@orchidjs/unicode-variants';
import * as T from './types';
// @ts-ignore TS2691 "An import path cannot end with a '.ts' extension"
import * as T from './types.ts';
/**

@@ -44,2 +41,3 @@ * A property getter resolving dot-notation

value = value + '';
if( token.regex == null ) return 0;
pos = value.search(token.regex);

@@ -54,11 +52,3 @@ if (pos === -1) return 0;

/**
*
* https://stackoverflow.com/questions/63006601/why-does-u-throw-an-invalid-escape-error
*/
export const escape_regex = (str:string):string => {
return (str + '').replace(/([\$\(\)\*\+\.\?\[\]\^\{\|\}\\])/gu, '\\$1');
};
/**

@@ -89,3 +79,3 @@ * Cast object property to an array if it exists and has a value

*/
export const iterate = (object:[]|{[key:string]:any}, callback:(value:any,key:number|string)=>any) => {
export const iterate = (object:[]|{[key:string]:any}, callback:(value:any,key:any)=>any) => {

@@ -92,0 +82,0 @@ if ( Array.isArray(object)) {

@@ -14,3 +14,3 @@ {

"description": "A library for textually searching arrays and hashes of objects by property (or multiple properties). Designed specifically for autocomplete.",
"version": "0.9.3",
"version": "1.0.0",
"license": "Apache-2.0",

@@ -41,2 +41,3 @@ "author": "Brian Reavis <brian@thirdroute.com>",

"@rollup/plugin-babel": "^5.3.0",
"@rollup/plugin-node-resolve": "^13.0.0",
"coveralls": "^3.1.0",

@@ -69,3 +70,5 @@ "humanize": "0.0.9",

],
"dependencies": {}
"dependencies": {
"@orchidjs/unicode-variants": "^1.0.2"
}
}

@@ -8,3 +8,3 @@ # sifter.js

* **Supports díåcritîçs.**<br>For example, if searching for "montana" and an item in the set has a value of "montaña", it will still be matched. Sorting will also play nicely with diacritics.
* **Supports díåcritîçs.**<br>For example, if searching for "montana" and an item in the set has a value of "montaña", it will still be matched. Sorting will also play nicely with diacritics. (using [unicode-variants](https://github.com/orchidjs/unicode-variants))
* **Smart scoring.**<br>Items are scored / sorted intelligently depending on where a match is found in the string (how close to the beginning) and what percentage of the string matches.

@@ -24,2 +24,4 @@ * **Multi-field sorting.**<br>When scores aren't enough to go by – like when getting results for an empty query – it can sort by one or more fields. For example, sort by a person's first name and last name without actually merging the properties to a single string.

```js
import {Sifter} from '@orchidjs/sifter';
var sifter = new Sifter([

@@ -26,0 +28,0 @@ {title: 'Annapurna I', location: 'Nepal', continent: 'Asia'},

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc