mirror of
https://github.com/krisk/Fuse.git
synced 2026-01-25 16:43:11 +00:00
978 lines
30 KiB
JavaScript
978 lines
30 KiB
JavaScript
/*!
|
|
* Fuse.js v3.0.1 - Lightweight fuzzy-search (http://fusejs.io)
|
|
*
|
|
* Copyright (c) 2012-2017 Kirollos Risk (http://kiro.me)
|
|
* All Rights Reserved. Apache Software License 2.0
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*/
|
|
(function webpackUniversalModuleDefinition(root, factory) {
|
|
if(typeof exports === 'object' && typeof module === 'object')
|
|
module.exports = factory();
|
|
else if(typeof define === 'function' && define.amd)
|
|
define("Fuse", [], factory);
|
|
else if(typeof exports === 'object')
|
|
exports["Fuse"] = factory();
|
|
else
|
|
root["Fuse"] = factory();
|
|
})(this, function() {
|
|
return /******/ (function(modules) { // webpackBootstrap
|
|
/******/ // The module cache
|
|
/******/ var installedModules = {};
|
|
/******/
|
|
/******/ // The require function
|
|
/******/ function __webpack_require__(moduleId) {
|
|
/******/
|
|
/******/ // Check if module is in cache
|
|
/******/ if(installedModules[moduleId]) {
|
|
/******/ return installedModules[moduleId].exports;
|
|
/******/ }
|
|
/******/ // Create a new module (and put it into the cache)
|
|
/******/ var module = installedModules[moduleId] = {
|
|
/******/ i: moduleId,
|
|
/******/ l: false,
|
|
/******/ exports: {}
|
|
/******/ };
|
|
/******/
|
|
/******/ // Execute the module function
|
|
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
|
|
/******/
|
|
/******/ // Flag the module as loaded
|
|
/******/ module.l = true;
|
|
/******/
|
|
/******/ // Return the exports of the module
|
|
/******/ return module.exports;
|
|
/******/ }
|
|
/******/
|
|
/******/
|
|
/******/ // expose the modules object (__webpack_modules__)
|
|
/******/ __webpack_require__.m = modules;
|
|
/******/
|
|
/******/ // expose the module cache
|
|
/******/ __webpack_require__.c = installedModules;
|
|
/******/
|
|
/******/ // identity function for calling harmony imports with the correct context
|
|
/******/ __webpack_require__.i = function(value) { return value; };
|
|
/******/
|
|
/******/ // define getter function for harmony exports
|
|
/******/ __webpack_require__.d = function(exports, name, getter) {
|
|
/******/ if(!__webpack_require__.o(exports, name)) {
|
|
/******/ Object.defineProperty(exports, name, {
|
|
/******/ configurable: false,
|
|
/******/ enumerable: true,
|
|
/******/ get: getter
|
|
/******/ });
|
|
/******/ }
|
|
/******/ };
|
|
/******/
|
|
/******/ // getDefaultExport function for compatibility with non-harmony modules
|
|
/******/ __webpack_require__.n = function(module) {
|
|
/******/ var getter = module && module.__esModule ?
|
|
/******/ function getDefault() { return module['default']; } :
|
|
/******/ function getModuleExports() { return module; };
|
|
/******/ __webpack_require__.d(getter, 'a', getter);
|
|
/******/ return getter;
|
|
/******/ };
|
|
/******/
|
|
/******/ // Object.prototype.hasOwnProperty.call
|
|
/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
|
|
/******/
|
|
/******/ // __webpack_public_path__
|
|
/******/ __webpack_require__.p = "";
|
|
/******/
|
|
/******/ // Load entry module and return exports
|
|
/******/ return __webpack_require__(__webpack_require__.s = 8);
|
|
/******/ })
|
|
/************************************************************************/
|
|
/******/ ([
|
|
/* 0 */
|
|
/***/ (function(module, exports, __webpack_require__) {
|
|
|
|
"use strict";
|
|
|
|
|
|
module.exports = function (obj) {
|
|
return Object.prototype.toString.call(obj) === '[object Array]';
|
|
};
|
|
|
|
/***/ }),
|
|
/* 1 */
|
|
/***/ (function(module, exports, __webpack_require__) {
|
|
|
|
"use strict";
|
|
|
|
|
|
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
|
|
|
|
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
|
|
|
|
var bitapRegexSearch = __webpack_require__(5);
|
|
var bitapSearch = __webpack_require__(7);
|
|
var patternAlphabet = __webpack_require__(4);
|
|
|
|
var Bitap = function () {
|
|
function Bitap(pattern, _ref) {
|
|
var _ref$location = _ref.location,
|
|
location = _ref$location === undefined ? 0 : _ref$location,
|
|
_ref$distance = _ref.distance,
|
|
distance = _ref$distance === undefined ? 100 : _ref$distance,
|
|
_ref$threshold = _ref.threshold,
|
|
threshold = _ref$threshold === undefined ? 0.6 : _ref$threshold,
|
|
_ref$maxPatternLength = _ref.maxPatternLength,
|
|
maxPatternLength = _ref$maxPatternLength === undefined ? 32 : _ref$maxPatternLength,
|
|
_ref$isCaseSensitive = _ref.isCaseSensitive,
|
|
isCaseSensitive = _ref$isCaseSensitive === undefined ? false : _ref$isCaseSensitive,
|
|
_ref$tokenSeparator = _ref.tokenSeparator,
|
|
tokenSeparator = _ref$tokenSeparator === undefined ? / +/g : _ref$tokenSeparator,
|
|
_ref$findAllMatches = _ref.findAllMatches,
|
|
findAllMatches = _ref$findAllMatches === undefined ? false : _ref$findAllMatches,
|
|
_ref$minMatchCharLeng = _ref.minMatchCharLength,
|
|
minMatchCharLength = _ref$minMatchCharLeng === undefined ? 1 : _ref$minMatchCharLeng;
|
|
|
|
_classCallCheck(this, Bitap);
|
|
|
|
this.options = {
|
|
location: location,
|
|
distance: distance,
|
|
threshold: threshold,
|
|
maxPatternLength: maxPatternLength,
|
|
isCaseSensitive: isCaseSensitive,
|
|
tokenSeparator: tokenSeparator,
|
|
findAllMatches: findAllMatches,
|
|
minMatchCharLength: minMatchCharLength
|
|
};
|
|
|
|
this.pattern = this.options.isCaseSensitive ? pattern : pattern.toLowerCase();
|
|
|
|
if (this.pattern.length <= maxPatternLength) {
|
|
this.patternAlphabet = patternAlphabet(this.pattern);
|
|
}
|
|
}
|
|
|
|
_createClass(Bitap, [{
|
|
key: 'search',
|
|
value: function search(text) {
|
|
if (!this.options.isCaseSensitive) {
|
|
text = text.toLowerCase();
|
|
}
|
|
|
|
// Exact match
|
|
if (this.pattern === text) {
|
|
return {
|
|
isMatch: true,
|
|
score: 0,
|
|
matchedIndices: [[0, text.length - 1]]
|
|
};
|
|
}
|
|
|
|
// When pattern length is greater than the machine word length, just do a a regex comparison
|
|
var _options = this.options,
|
|
maxPatternLength = _options.maxPatternLength,
|
|
tokenSeparator = _options.tokenSeparator;
|
|
|
|
if (this.pattern.length > maxPatternLength) {
|
|
return bitapRegexSearch(text, this.pattern, tokenSeparator);
|
|
}
|
|
|
|
// Otherwise, use Bitap algorithm
|
|
var _options2 = this.options,
|
|
location = _options2.location,
|
|
distance = _options2.distance,
|
|
threshold = _options2.threshold,
|
|
findAllMatches = _options2.findAllMatches,
|
|
minMatchCharLength = _options2.minMatchCharLength;
|
|
|
|
return bitapSearch(text, this.pattern, this.patternAlphabet, {
|
|
location: location,
|
|
distance: distance,
|
|
threshold: threshold,
|
|
findAllMatches: findAllMatches,
|
|
minMatchCharLength: minMatchCharLength
|
|
});
|
|
}
|
|
}]);
|
|
|
|
return Bitap;
|
|
}();
|
|
|
|
// let x = new Bitap("od mn war", {})
|
|
// let result = x.search("Old Man's War")
|
|
// console.log(result)
|
|
|
|
module.exports = Bitap;
|
|
|
|
/***/ }),
|
|
/* 2 */
|
|
/***/ (function(module, exports, __webpack_require__) {
|
|
|
|
"use strict";
|
|
|
|
|
|
var isArray = __webpack_require__(0);
|
|
|
|
var deepValue = function deepValue(obj, path, list) {
|
|
if (!path) {
|
|
// If there's no path left, we've gotten to the object we care about.
|
|
list.push(obj);
|
|
} else {
|
|
var dotIndex = path.indexOf('.');
|
|
var firstSegment = path;
|
|
var remaining = null;
|
|
|
|
if (dotIndex !== -1) {
|
|
firstSegment = path.slice(0, dotIndex);
|
|
remaining = path.slice(dotIndex + 1);
|
|
}
|
|
|
|
var value = obj[firstSegment];
|
|
|
|
if (value !== null && value !== undefined) {
|
|
if (!remaining && (typeof value === 'string' || typeof value === 'number')) {
|
|
list.push(value);
|
|
} else if (isArray(value)) {
|
|
// Search each item in the array.
|
|
for (var i = 0, len = value.length; i < len; i += 1) {
|
|
deepValue(value[i], remaining, list);
|
|
}
|
|
} else if (remaining) {
|
|
// An object. Recurse further.
|
|
deepValue(value, remaining, list);
|
|
}
|
|
}
|
|
}
|
|
|
|
return list;
|
|
};
|
|
|
|
module.exports = function (obj, path) {
|
|
return deepValue(obj, path, []);
|
|
};
|
|
|
|
/***/ }),
|
|
/* 3 */
|
|
/***/ (function(module, exports, __webpack_require__) {
|
|
|
|
"use strict";
|
|
|
|
|
|
module.exports = function () {
|
|
var matchmask = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
|
|
var minMatchCharLength = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 1;
|
|
|
|
var matchedIndices = [];
|
|
var start = -1;
|
|
var end = -1;
|
|
var i = 0;
|
|
|
|
for (var len = matchmask.length; i < len; i += 1) {
|
|
var match = matchmask[i];
|
|
if (match && start === -1) {
|
|
start = i;
|
|
} else if (!match && start !== -1) {
|
|
end = i - 1;
|
|
if (end - start + 1 >= minMatchCharLength) {
|
|
matchedIndices.push([start, end]);
|
|
}
|
|
start = -1;
|
|
}
|
|
}
|
|
|
|
// (i-1 - start) + 1 => i - start
|
|
if (matchmask[i - 1] && i - start >= minMatchCharLength) {
|
|
matchedIndices.push([start, i - 1]);
|
|
}
|
|
|
|
return matchedIndices;
|
|
};
|
|
|
|
/***/ }),
|
|
/* 4 */
|
|
/***/ (function(module, exports, __webpack_require__) {
|
|
|
|
"use strict";
|
|
|
|
|
|
module.exports = function (pattern) {
|
|
var mask = {};
|
|
var len = pattern.length;
|
|
|
|
for (var i = 0; i < len; i += 1) {
|
|
mask[pattern.charAt(i)] = 0;
|
|
}
|
|
|
|
for (var _i = 0; _i < len; _i += 1) {
|
|
mask[pattern.charAt(_i)] |= 1 << len - _i - 1;
|
|
}
|
|
|
|
return mask;
|
|
};
|
|
|
|
/***/ }),
|
|
/* 5 */
|
|
/***/ (function(module, exports, __webpack_require__) {
|
|
|
|
"use strict";
|
|
|
|
|
|
module.exports = function (text, pattern) {
|
|
var tokenSeparator = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : / +/g;
|
|
|
|
var matches = text.match(new RegExp(pattern.replace(tokenSeparator, '|')));
|
|
var isMatch = !!matches;
|
|
var matchedIndices = [];
|
|
|
|
if (isMatch) {
|
|
for (i = 0, matchesLen = matches.length; i < matchesLen; i += 1) {
|
|
match = matches[i];
|
|
matchedIndices.push([text.indexOf(match), match.length - 1]);
|
|
}
|
|
}
|
|
|
|
return {
|
|
// TODO: revisit this score
|
|
score: isMatched ? 0.5 : 1,
|
|
isMatch: isMatch,
|
|
matchedIndices: matchedIndices
|
|
};
|
|
};
|
|
|
|
/***/ }),
|
|
/* 6 */
|
|
/***/ (function(module, exports, __webpack_require__) {
|
|
|
|
"use strict";
|
|
|
|
|
|
module.exports = function (pattern, _ref) {
|
|
var _ref$errors = _ref.errors,
|
|
errors = _ref$errors === undefined ? 0 : _ref$errors,
|
|
_ref$currentLocation = _ref.currentLocation,
|
|
currentLocation = _ref$currentLocation === undefined ? 0 : _ref$currentLocation,
|
|
_ref$expectedLocation = _ref.expectedLocation,
|
|
expectedLocation = _ref$expectedLocation === undefined ? 0 : _ref$expectedLocation,
|
|
_ref$distance = _ref.distance,
|
|
distance = _ref$distance === undefined ? 100 : _ref$distance;
|
|
|
|
var accuracy = errors / pattern.length;
|
|
var proximity = Math.abs(expectedLocation - currentLocation);
|
|
|
|
if (!distance) {
|
|
// Dodge divide by zero error.
|
|
return proximity ? 1.0 : accuracy;
|
|
}
|
|
|
|
return accuracy + proximity / distance;
|
|
};
|
|
|
|
/***/ }),
|
|
/* 7 */
|
|
/***/ (function(module, exports, __webpack_require__) {
|
|
|
|
"use strict";
|
|
|
|
|
|
var bitapScore = __webpack_require__(6);
|
|
var matchedIndices = __webpack_require__(3);
|
|
|
|
module.exports = function (text, pattern, patternAlphabet, _ref) {
|
|
var _ref$location = _ref.location,
|
|
location = _ref$location === undefined ? 0 : _ref$location,
|
|
_ref$distance = _ref.distance,
|
|
distance = _ref$distance === undefined ? 100 : _ref$distance,
|
|
_ref$threshold = _ref.threshold,
|
|
threshold = _ref$threshold === undefined ? 0.6 : _ref$threshold,
|
|
_ref$findAllMatches = _ref.findAllMatches,
|
|
findAllMatches = _ref$findAllMatches === undefined ? false : _ref$findAllMatches,
|
|
_ref$minMatchCharLeng = _ref.minMatchCharLength,
|
|
minMatchCharLength = _ref$minMatchCharLeng === undefined ? 1 : _ref$minMatchCharLeng;
|
|
|
|
var expectedLocation = location;
|
|
// Set starting location at beginning text and initialize the alphabet.
|
|
var textLen = text.length;
|
|
// Highest score beyond which we give up.
|
|
var currentThreshold = threshold;
|
|
// Is there a nearby exact match? (speedup)
|
|
var bestLocation = text.indexOf(pattern, expectedLocation);
|
|
|
|
var patternLen = pattern.length;
|
|
|
|
// a mask of the matches
|
|
var matchMask = [];
|
|
for (var i = 0; i < textLen; i += 1) {
|
|
matchMask[i] = 0;
|
|
}
|
|
|
|
if (bestLocation != -1) {
|
|
var score = bitapScore(pattern, {
|
|
errors: 0,
|
|
currentLocation: bestLocation,
|
|
expectedLocation: expectedLocation,
|
|
distance: distance
|
|
});
|
|
currentThreshold = Math.min(score, currentThreshold);
|
|
|
|
// What about in the other direction? (speed up)
|
|
bestLocation = text.lastIndexOf(pattern, expectedLocation + patternLen);
|
|
|
|
if (bestLocation != -1) {
|
|
var _score = bitapScore(pattern, {
|
|
errors: 0,
|
|
currentLocation: bestLocation,
|
|
expectedLocation: expectedLocation,
|
|
distance: distance
|
|
});
|
|
currentThreshold = Math.min(_score, currentThreshold);
|
|
}
|
|
}
|
|
|
|
// Reset the best location
|
|
bestLocation = -1;
|
|
|
|
var lastBitArr = [];
|
|
var finalScore = 1;
|
|
var binMax = patternLen + textLen;
|
|
|
|
var mask = 1 << patternLen - 1;
|
|
|
|
for (var _i = 0; _i < patternLen; _i += 1) {
|
|
// Scan for the best match; each iteration allows for one more error.
|
|
// Run a binary search to determine how far from the match location we can stray
|
|
// at this error level.
|
|
var binMin = 0;
|
|
var binMid = binMax;
|
|
|
|
while (binMin < binMid) {
|
|
var _score3 = bitapScore(pattern, {
|
|
errors: _i,
|
|
currentLocation: expectedLocation + binMid,
|
|
expectedLocation: expectedLocation,
|
|
distance: distance
|
|
});
|
|
|
|
if (_score3 <= currentThreshold) {
|
|
binMin = binMid;
|
|
} else {
|
|
binMax = binMid;
|
|
}
|
|
|
|
binMid = Math.floor((binMax - binMin) / 2 + binMin);
|
|
}
|
|
|
|
// Use the result from this iteration as the maximum for the next.
|
|
binMax = binMid;
|
|
|
|
var start = Math.max(1, expectedLocation - binMid + 1);
|
|
var finish = findAllMatches ? textLen : Math.min(expectedLocation + binMid, textLen) + patternLen;
|
|
|
|
// Initialize the bit array
|
|
var bitArr = Array(finish + 2);
|
|
|
|
bitArr[finish + 1] = (1 << _i) - 1;
|
|
|
|
for (var j = finish; j >= start; j -= 1) {
|
|
var currentLocation = j - 1;
|
|
var charMatch = patternAlphabet[text.charAt(currentLocation)];
|
|
|
|
if (charMatch) {
|
|
matchMask[currentLocation] = 1;
|
|
}
|
|
|
|
// First pass: exact match
|
|
bitArr[j] = (bitArr[j + 1] << 1 | 1) & charMatch;
|
|
|
|
// Subsequent passes: fuzzy match
|
|
if (_i !== 0) {
|
|
bitArr[j] |= (lastBitArr[j + 1] | lastBitArr[j]) << 1 | 1 | lastBitArr[j + 1];
|
|
}
|
|
|
|
if (bitArr[j] & mask) {
|
|
finalScore = bitapScore(pattern, {
|
|
errors: _i,
|
|
currentLocation: currentLocation,
|
|
expectedLocation: expectedLocation,
|
|
distance: distance
|
|
});
|
|
|
|
// This match will almost certainly be better than any existing match.
|
|
// But check anyway.
|
|
if (finalScore <= currentThreshold) {
|
|
// Indeed it is
|
|
currentThreshold = finalScore;
|
|
bestLocation = currentLocation;
|
|
|
|
// Already passed `loc`, downhill from here on in.
|
|
if (bestLocation <= expectedLocation) {
|
|
break;
|
|
}
|
|
|
|
// When passing `bestLocation`, don't exceed our current distance from `expectedLocation`.
|
|
start = Math.max(1, 2 * expectedLocation - bestLocation);
|
|
}
|
|
}
|
|
}
|
|
|
|
// No hope for a (better) match at greater error levels.
|
|
var _score2 = bitapScore(pattern, {
|
|
errors: _i + 1,
|
|
currentLocation: expectedLocation,
|
|
expectedLocation: expectedLocation,
|
|
distance: distance
|
|
});
|
|
|
|
if (_score2 > currentThreshold) {
|
|
break;
|
|
}
|
|
|
|
lastBitArr = bitArr;
|
|
}
|
|
|
|
// Count exact matches (those with a score of 0) to be "almost" exact
|
|
return {
|
|
isMatch: bestLocation >= 0,
|
|
score: finalScore === 0 ? 0.001 : finalScore,
|
|
matchedIndices: matchedIndices(matchMask, minMatchCharLength)
|
|
};
|
|
};
|
|
|
|
/***/ }),
|
|
/* 8 */
|
|
/***/ (function(module, exports, __webpack_require__) {
|
|
|
|
"use strict";
|
|
|
|
|
|
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
|
|
|
|
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
|
|
|
|
var Bitap = __webpack_require__(1);
|
|
var deepValue = __webpack_require__(2);
|
|
var isArray = __webpack_require__(0);
|
|
|
|
var Fuse = function () {
|
|
function Fuse(list, _ref) {
|
|
var _ref$location = _ref.location,
|
|
location = _ref$location === undefined ? 0 : _ref$location,
|
|
_ref$distance = _ref.distance,
|
|
distance = _ref$distance === undefined ? 100 : _ref$distance,
|
|
_ref$threshold = _ref.threshold,
|
|
threshold = _ref$threshold === undefined ? 0.6 : _ref$threshold,
|
|
_ref$maxPatternLength = _ref.maxPatternLength,
|
|
maxPatternLength = _ref$maxPatternLength === undefined ? 32 : _ref$maxPatternLength,
|
|
_ref$caseSensitive = _ref.caseSensitive,
|
|
caseSensitive = _ref$caseSensitive === undefined ? false : _ref$caseSensitive,
|
|
_ref$tokenSeparator = _ref.tokenSeparator,
|
|
tokenSeparator = _ref$tokenSeparator === undefined ? / +/g : _ref$tokenSeparator,
|
|
_ref$findAllMatches = _ref.findAllMatches,
|
|
findAllMatches = _ref$findAllMatches === undefined ? false : _ref$findAllMatches,
|
|
_ref$minMatchCharLeng = _ref.minMatchCharLength,
|
|
minMatchCharLength = _ref$minMatchCharLeng === undefined ? 1 : _ref$minMatchCharLeng,
|
|
_ref$id = _ref.id,
|
|
id = _ref$id === undefined ? null : _ref$id,
|
|
_ref$keys = _ref.keys,
|
|
keys = _ref$keys === undefined ? [] : _ref$keys,
|
|
_ref$shouldSort = _ref.shouldSort,
|
|
shouldSort = _ref$shouldSort === undefined ? true : _ref$shouldSort,
|
|
_ref$getFn = _ref.getFn,
|
|
getFn = _ref$getFn === undefined ? deepValue : _ref$getFn,
|
|
_ref$sortFn = _ref.sortFn,
|
|
sortFn = _ref$sortFn === undefined ? function (a, b) {
|
|
return a.score - b.score;
|
|
} : _ref$sortFn,
|
|
_ref$tokenize = _ref.tokenize,
|
|
tokenize = _ref$tokenize === undefined ? false : _ref$tokenize,
|
|
_ref$matchAllTokens = _ref.matchAllTokens,
|
|
matchAllTokens = _ref$matchAllTokens === undefined ? false : _ref$matchAllTokens,
|
|
_ref$includeMatches = _ref.includeMatches,
|
|
includeMatches = _ref$includeMatches === undefined ? false : _ref$includeMatches,
|
|
_ref$includeScore = _ref.includeScore,
|
|
includeScore = _ref$includeScore === undefined ? false : _ref$includeScore,
|
|
_ref$verbose = _ref.verbose,
|
|
verbose = _ref$verbose === undefined ? false : _ref$verbose;
|
|
|
|
_classCallCheck(this, Fuse);
|
|
|
|
this.options = {
|
|
location: location,
|
|
distance: distance,
|
|
threshold: threshold,
|
|
maxPatternLength: maxPatternLength,
|
|
isCaseSensitive: caseSensitive,
|
|
tokenSeparator: tokenSeparator,
|
|
findAllMatches: findAllMatches,
|
|
minMatchCharLength: minMatchCharLength,
|
|
id: id,
|
|
keys: keys,
|
|
includeMatches: includeMatches,
|
|
includeScore: includeScore,
|
|
shouldSort: shouldSort,
|
|
getFn: getFn,
|
|
sortFn: sortFn,
|
|
verbose: verbose,
|
|
tokenize: tokenize,
|
|
matchAllTokens: matchAllTokens
|
|
};
|
|
|
|
this.set(list);
|
|
}
|
|
|
|
_createClass(Fuse, [{
|
|
key: 'set',
|
|
value: function set(list) {
|
|
this.list = list;
|
|
return list;
|
|
}
|
|
}, {
|
|
key: 'search',
|
|
value: function search(pattern) {
|
|
this._log('---------\nSearch pattern: "' + pattern + '"');
|
|
|
|
var _prepareSearchers2 = this._prepareSearchers(pattern),
|
|
tokenSearchers = _prepareSearchers2.tokenSearchers,
|
|
fullSearcher = _prepareSearchers2.fullSearcher;
|
|
|
|
var _search2 = this._search(tokenSearchers, fullSearcher),
|
|
weights = _search2.weights,
|
|
results = _search2.results;
|
|
|
|
this._computeScore(weights, results);
|
|
|
|
if (this.options.shouldSort) {
|
|
this._sort(results);
|
|
}
|
|
|
|
return this._format(results);
|
|
}
|
|
}, {
|
|
key: '_prepareSearchers',
|
|
value: function _prepareSearchers() {
|
|
var pattern = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
|
|
|
|
var tokenSearchers = [];
|
|
|
|
if (this.options.tokenize) {
|
|
// Tokenize on the separator
|
|
var tokens = pattern.split(this.options.tokenSeparator);
|
|
for (var i = 0, len = tokens.length; i < len; i += 1) {
|
|
tokenSearchers.push(new Bitap(tokens[i], this.options));
|
|
}
|
|
}
|
|
|
|
var fullSearcher = new Bitap(pattern, this.options);
|
|
|
|
return { tokenSearchers: tokenSearchers, fullSearcher: fullSearcher };
|
|
}
|
|
}, {
|
|
key: '_search',
|
|
value: function _search() {
|
|
var tokenSearchers = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
|
|
var fullSearcher = arguments[1];
|
|
|
|
var list = this.list;
|
|
var resultMap = {};
|
|
var results = [];
|
|
|
|
// Check the first item in the list, if it's a string, then we assume
|
|
// that every item in the list is also a string, and thus it's a flattened array.
|
|
if (typeof list[0] === 'string') {
|
|
// Iterate over every item
|
|
for (var i = 0, len = list.length; i < len; i += 1) {
|
|
this._analyze({
|
|
key: '',
|
|
value: list[i],
|
|
record: i,
|
|
index: i
|
|
}, {
|
|
resultMap: resultMap,
|
|
results: results,
|
|
tokenSearchers: tokenSearchers,
|
|
fullSearcher: fullSearcher
|
|
});
|
|
}
|
|
|
|
return { weights: null, results: results };
|
|
}
|
|
|
|
// Otherwise, the first item is an Object (hopefully), and thus the searching
|
|
// is done on the values of the keys of each item.
|
|
var weights = {};
|
|
for (var _i = 0, _len = list.length; _i < _len; _i += 1) {
|
|
var item = list[_i];
|
|
// Iterate over every key
|
|
for (var j = 0, keysLen = this.options.keys.length; j < keysLen; j += 1) {
|
|
var key = this.options.keys[j];
|
|
if (typeof key !== 'string') {
|
|
weights[key.name] = {
|
|
weight: 1 - key.weight || 1
|
|
};
|
|
if (key.weight <= 0 || key.weight > 1) {
|
|
throw new Error('Key weight has to be > 0 and <= 1');
|
|
}
|
|
key = key.name;
|
|
} else {
|
|
weights[key] = {
|
|
weight: 1
|
|
};
|
|
}
|
|
|
|
this._analyze({
|
|
key: key,
|
|
value: this.options.getFn(item, key),
|
|
record: item,
|
|
index: _i
|
|
}, {
|
|
resultMap: resultMap,
|
|
results: results,
|
|
tokenSearchers: tokenSearchers,
|
|
fullSearcher: fullSearcher
|
|
});
|
|
}
|
|
}
|
|
|
|
return { weights: weights, results: results };
|
|
}
|
|
}, {
|
|
key: '_analyze',
|
|
value: function _analyze(_ref2, _ref3) {
|
|
var key = _ref2.key,
|
|
value = _ref2.value,
|
|
record = _ref2.record,
|
|
index = _ref2.index;
|
|
var _ref3$tokenSearchers = _ref3.tokenSearchers,
|
|
tokenSearchers = _ref3$tokenSearchers === undefined ? [] : _ref3$tokenSearchers,
|
|
_ref3$fullSearcher = _ref3.fullSearcher,
|
|
fullSearcher = _ref3$fullSearcher === undefined ? [] : _ref3$fullSearcher,
|
|
_ref3$resultMap = _ref3.resultMap,
|
|
resultMap = _ref3$resultMap === undefined ? {} : _ref3$resultMap,
|
|
_ref3$results = _ref3.results,
|
|
results = _ref3$results === undefined ? [] : _ref3$results;
|
|
|
|
// Check if the texvaluet can be searched
|
|
if (value === undefined || value === null) {
|
|
return;
|
|
}
|
|
|
|
var exists = false;
|
|
var averageScore = -1;
|
|
var numTextMatches = 0;
|
|
|
|
if (typeof value === 'string') {
|
|
this._log('\nKey: ' + (key === '' ? '-' : key));
|
|
|
|
var mainSearchResult = fullSearcher.search(value);
|
|
this._log('Full text: "' + value + '", score: ' + mainSearchResult.score);
|
|
|
|
if (this.options.tokenize) {
|
|
var words = value.split(this.options.tokenSeparator);
|
|
var scores = [];
|
|
|
|
for (var i = 0; i < tokenSearchers.length; i += 1) {
|
|
var tokenSearcher = tokenSearchers[i];
|
|
|
|
this._log('\nPattern: "' + tokenSearcher.pattern + '"');
|
|
|
|
// let tokenScores = []
|
|
var hasMatchInText = false;
|
|
|
|
for (var j = 0; j < words.length; j += 1) {
|
|
var word = words[j];
|
|
var tokenSearchResult = tokenSearcher.search(word);
|
|
var obj = {};
|
|
if (tokenSearchResult.isMatch) {
|
|
obj[word] = tokenSearchResult.score;
|
|
exists = true;
|
|
hasMatchInText = true;
|
|
scores.push(tokenSearchResult.score);
|
|
} else {
|
|
obj[word] = 1;
|
|
if (!this.options.matchAllTokens) {
|
|
scores.push(1);
|
|
}
|
|
}
|
|
this._log('Token: "' + word + '", score: ' + obj[word]);
|
|
// tokenScores.push(obj)
|
|
}
|
|
|
|
if (hasMatchInText) {
|
|
numTextMatches += 1;
|
|
}
|
|
}
|
|
|
|
averageScore = scores[0];
|
|
var scoresLen = scores.length;
|
|
for (var _i2 = 1; _i2 < scoresLen; _i2 += 1) {
|
|
averageScore += scores[_i2];
|
|
}
|
|
averageScore = averageScore / scoresLen;
|
|
|
|
this._log('Token score average:', averageScore);
|
|
}
|
|
|
|
var finalScore = mainSearchResult.score;
|
|
if (averageScore > -1) {
|
|
finalScore = (finalScore + averageScore) / 2;
|
|
}
|
|
|
|
this._log('Score average:', finalScore);
|
|
|
|
var checkTextMatches = this.options.tokenize && this.options.matchAllTokens ? numTextMatches >= tokenSearchers.length : true;
|
|
|
|
this._log('\nCheck Matches: ' + checkTextMatches);
|
|
|
|
// If a match is found, add the item to <rawResults>, including its score
|
|
if ((exists || mainSearchResult.isMatch) && checkTextMatches) {
|
|
// Check if the item already exists in our results
|
|
var existingResult = resultMap[index];
|
|
|
|
if (existingResult) {
|
|
// Use the lowest score
|
|
// existingResult.score, bitapResult.score
|
|
existingResult.output.push({
|
|
key: key,
|
|
score: finalScore,
|
|
matchedIndices: mainSearchResult.matchedIndices
|
|
});
|
|
} else {
|
|
// Add it to the raw result list
|
|
resultMap[index] = {
|
|
item: record,
|
|
output: [{
|
|
key: key,
|
|
score: finalScore,
|
|
matchedIndices: mainSearchResult.matchedIndices
|
|
}]
|
|
};
|
|
|
|
results.push(resultMap[index]);
|
|
}
|
|
}
|
|
} else if (isArray(value)) {
|
|
for (var _i3 = 0, len = value.length; _i3 < len; _i3 += 1) {
|
|
this._analyze({
|
|
key: key,
|
|
value: value[_i3],
|
|
record: record,
|
|
index: index
|
|
}, {
|
|
resultMap: resultMap,
|
|
results: results,
|
|
tokenSearchers: tokenSearchers,
|
|
fullSearcher: fullSearcher
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}, {
|
|
key: '_computeScore',
|
|
value: function _computeScore(weights, results) {
|
|
this._log('\n\nComputing score:\n');
|
|
|
|
for (var i = 0, len = results.length; i < len; i += 1) {
|
|
var output = results[i].output;
|
|
var scoreLen = output.length;
|
|
|
|
var totalScore = 0;
|
|
var bestScore = 1;
|
|
|
|
for (var j = 0; j < scoreLen; j += 1) {
|
|
var score = output[j].score;
|
|
var weight = weights ? weights[output[j].key].weight : 1;
|
|
var nScore = score * weight;
|
|
|
|
if (weight !== 1) {
|
|
bestScore = Math.min(bestScore, nScore);
|
|
} else {
|
|
output[j].nScore = nScore;
|
|
totalScore += nScore;
|
|
}
|
|
}
|
|
|
|
results[i].score = bestScore === 1 ? totalScore / scoreLen : bestScore;
|
|
|
|
this._log(results[i]);
|
|
}
|
|
}
|
|
}, {
|
|
key: '_sort',
|
|
value: function _sort(results) {
|
|
this._log('\n\nSorting....');
|
|
results.sort(this.options.sortFn);
|
|
}
|
|
}, {
|
|
key: '_format',
|
|
value: function _format(results) {
|
|
var finalOutput = [];
|
|
|
|
this._log('\n\nOutput:\n\n', results);
|
|
|
|
var transformers = [];
|
|
|
|
if (this.options.includeMatches) {
|
|
transformers.push(function (result, data) {
|
|
var output = result.output;
|
|
data.matches = [];
|
|
|
|
for (var i = 0, len = output.length; i < len; i += 1) {
|
|
var item = output[i];
|
|
var obj = {
|
|
indices: item.matchedIndices
|
|
};
|
|
if (item.key) {
|
|
obj.key = item.key;
|
|
}
|
|
data.matches.push(obj);
|
|
}
|
|
});
|
|
}
|
|
|
|
if (this.options.includeScore) {
|
|
transformers.push(function (result, data) {
|
|
data.score = result.score;
|
|
});
|
|
}
|
|
|
|
for (var i = 0, len = results.length; i < len; i += 1) {
|
|
var result = results[i];
|
|
|
|
if (this.options.id) {
|
|
result.item = this.options.getFn(result.item, this.options.id)[0];
|
|
}
|
|
|
|
if (!transformers.length) {
|
|
finalOutput.push(result.item);
|
|
continue;
|
|
}
|
|
|
|
var data = {
|
|
item: result.item
|
|
};
|
|
|
|
for (var j = 0, _len2 = transformers.length; j < _len2; j += 1) {
|
|
transformers[j](result, data);
|
|
}
|
|
|
|
finalOutput.push(data);
|
|
}
|
|
|
|
return finalOutput;
|
|
}
|
|
}, {
|
|
key: '_log',
|
|
value: function _log() {
|
|
if (this.options.verbose) {
|
|
var _console;
|
|
|
|
(_console = console).log.apply(_console, arguments);
|
|
}
|
|
}
|
|
}]);
|
|
|
|
return Fuse;
|
|
}();
|
|
|
|
module.exports = Fuse;
|
|
|
|
/***/ })
|
|
/******/ ]);
|
|
});
|
|
//# sourceMappingURL=fuse.js.map
|