UNPKG

js-worker-search-x

Version:

JavaScript client-side search API with web-worker support

563 lines (436 loc) 16.9 kB
/******/ (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] = { /******/ exports: {}, /******/ id: moduleId, /******/ loaded: false /******/ }; /******/ // Execute the module function /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); /******/ // Flag the module as loaded /******/ module.loaded = 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; /******/ // __webpack_public_path__ /******/ __webpack_require__.p = ""; /******/ // Load entry module and return exports /******/ return __webpack_require__(0); /******/ }) /************************************************************************/ /******/ ([ /* 0 */ /***/ function(module, exports, __webpack_require__) { "use strict"; var _util = __webpack_require__(1); /** * Search entry point to web worker. * Builds search index and performs searches on separate thread from the ui. */ var searchUtility = new _util.SearchUtility(); self.addEventListener("message", function (event) { var data = event.data; var method = data.method; switch (method) { case "indexDocument": var uid = data.uid, text = data.text; searchUtility.indexDocument(uid, text); break; case "search": var callbackId = data.callbackId, query = data.query; var results = searchUtility.search(query); self.postMessage({ callbackId: callbackId, results: results }); break; case "setCaseSensitive": var caseSensitive = data.caseSensitive; searchUtility.setCaseSensitive(caseSensitive); break; case "setIndexMode": var indexMode = data.indexMode; searchUtility.setIndexMode(indexMode); break; case "setMatchAnyToken": var matchAnyToken = data.matchAnyToken; searchUtility.setMatchAnyToken(matchAnyToken); break; case "setTokenizePattern": var tokenizePattern = data.tokenizePattern; searchUtility.setTokenizePattern(tokenizePattern); break; } }, false); /***/ }, /* 1 */ /***/ function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.SearchUtility = exports.INDEX_MODES = undefined; var _SearchUtility = __webpack_require__(2); var _SearchUtility2 = _interopRequireDefault(_SearchUtility); var _constants = __webpack_require__(3); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } exports.default = _SearchUtility2.default; exports.INDEX_MODES = _constants.INDEX_MODES; exports.SearchUtility = _SearchUtility2.default; /***/ }, /* 2 */ /***/ function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); 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; }; }(); var _constants = __webpack_require__(3); var _SearchIndex = __webpack_require__(4); var _SearchIndex2 = _interopRequireDefault(_SearchIndex); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } /** * Synchronous client-side full-text search utility. * Forked from JS search (github.com/bvaughn/js-search). */ var SearchUtility = function () { /** * Constructor. * * @param indexMode See #setIndexMode * @param tokenizePattern See #setTokenizePattern * @param caseSensitive See #setCaseSensitive * @param matchAnyToken See #setMatchAnyToken */ function SearchUtility() { var _this = this; var _ref = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}, _ref$caseSensitive = _ref.caseSensitive, caseSensitive = _ref$caseSensitive === undefined ? false : _ref$caseSensitive, _ref$indexMode = _ref.indexMode, indexMode = _ref$indexMode === undefined ? _constants.INDEX_MODES.ALL_SUBSTRINGS : _ref$indexMode, _ref$matchAnyToken = _ref.matchAnyToken, matchAnyToken = _ref$matchAnyToken === undefined ? false : _ref$matchAnyToken, _ref$tokenizePattern = _ref.tokenizePattern, tokenizePattern = _ref$tokenizePattern === undefined ? /\s+/ : _ref$tokenizePattern; _classCallCheck(this, SearchUtility); this.indexDocument = function (uid, text) { _this._uids[uid] = true; var fieldTokens = _this._tokenize(_this._sanitize(text)); fieldTokens.forEach(function (fieldToken) { var expandedTokens = _this._expandToken(fieldToken); expandedTokens.forEach(function (expandedToken) { _this._searchIndex.indexDocument(expandedToken, uid); }); }); return _this; }; this.search = function (query) { if (!query) { return Object.keys(_this._uids); } else { var tokens = _this._tokenize(_this._sanitize(query)); return _this._searchIndex.search(tokens, _this._matchAnyToken); } }; this.terminate = function () {}; this._caseSensitive = caseSensitive; this._indexMode = indexMode; this._matchAnyToken = matchAnyToken; this._tokenizePattern = tokenizePattern; this._searchIndex = new _SearchIndex2.default(); this._uids = {}; } /** * Returns a constant representing the current case-sensitive bit. */ _createClass(SearchUtility, [{ key: "getCaseSensitive", value: function getCaseSensitive() { return this._caseSensitive; } /** * Returns a constant representing the current index mode. */ }, { key: "getIndexMode", value: function getIndexMode() { return this._indexMode; } /** * Returns a constant representing the current match-any-token bit. */ }, { key: "getMatchAnyToken", value: function getMatchAnyToken() { return this._matchAnyToken; } /** * Returns a constant representing the current tokenize pattern. */ }, { key: "getTokenizePattern", value: function getTokenizePattern() { return this._tokenizePattern; } /** * Adds or updates a uid in the search index and associates it with the specified text. * Note that at this time uids can only be added or updated in the index, not removed. * * @param uid Uniquely identifies a searchable object * @param text Text to associate with uid */ /** * Searches the current index for the specified query text. * Only uids matching all of the words within the text will be accepted, * unless matchAny is set to true. * If an empty query string is provided all indexed uids will be returned. * * Document searches are case-insensitive by default (e.g. "search" will match "Search"). * Document searches use substring matching by default (e.g. "na" and "me" will both match "name"). * * @param query Searchable query text * @return Array of uids */ }, { key: "setCaseSensitive", /** * Sets a new case-sensitive bit */ value: function setCaseSensitive(caseSensitive) { this._caseSensitive = caseSensitive; } /** * Sets a new index mode. * See util/constants/INDEX_MODES */ }, { key: "setIndexMode", value: function setIndexMode(indexMode) { if (Object.keys(this._uids).length > 0) { throw Error("indexMode cannot be changed once documents have been indexed"); } this._indexMode = indexMode; } /** * Sets a new match-any-token bit */ }, { key: "setMatchAnyToken", value: function setMatchAnyToken(matchAnyToken) { this._matchAnyToken = matchAnyToken; } /** * Sets a new tokenize pattern (regular expression) */ }, { key: "setTokenizePattern", value: function setTokenizePattern(pattern) { this._tokenizePattern = pattern; } /** * Added to make class adhere to interface. Add cleanup code as needed. */ }, { key: "_expandToken", /** * Index strategy based on 'all-substrings-index-strategy.ts' in github.com/bvaughn/js-search/ * * @private */ value: function _expandToken(token) { switch (this._indexMode) { case _constants.INDEX_MODES.EXACT_WORDS: return [token]; case _constants.INDEX_MODES.PREFIXES: return this._expandPrefixTokens(token); case _constants.INDEX_MODES.ALL_SUBSTRINGS: default: return this._expandAllSubstringTokens(token); } } }, { key: "_expandAllSubstringTokens", value: function _expandAllSubstringTokens(token) { var expandedTokens = []; // String.prototype.charAt() may return surrogate halves instead of whole characters. // When this happens in the context of a web-worker it can cause Chrome to crash. // Catching the error is a simple solution for now; in the future I may try to better support non-BMP characters. // Resources: // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/charAt // https://mathiasbynens.be/notes/javascript-unicode try { for (var i = 0, length = token.length; i < length; ++i) { var substring = ""; for (var j = i; j < length; ++j) { substring += token.charAt(j); expandedTokens.push(substring); } } } catch (error) { console.error("Unable to parse token \"" + token + "\" " + error); } return expandedTokens; } }, { key: "_expandPrefixTokens", value: function _expandPrefixTokens(token) { var expandedTokens = []; // String.prototype.charAt() may return surrogate halves instead of whole characters. // When this happens in the context of a web-worker it can cause Chrome to crash. // Catching the error is a simple solution for now; in the future I may try to better support non-BMP characters. // Resources: // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/charAt // https://mathiasbynens.be/notes/javascript-unicode try { for (var i = 0, length = token.length; i < length; ++i) { expandedTokens.push(token.substr(0, i + 1)); } } catch (error) { console.error("Unable to parse token \"" + token + "\" " + error); } return expandedTokens; } /** * @private */ }, { key: "_sanitize", value: function _sanitize(string) { return this._caseSensitive ? string.trim() : string.trim().toLocaleLowerCase(); } /** * @private */ }, { key: "_tokenize", value: function _tokenize(text) { return text.split(this._tokenizePattern).filter(function (text) { return text; }); // Remove empty tokens } }]); return SearchUtility; }(); exports.default = SearchUtility; /***/ }, /* 3 */ /***/ function(module, exports) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var INDEX_MODES = exports.INDEX_MODES = { // Indexes for all substring searches (e.g. the term "cat" is indexed as "c", "ca", "cat", "a", "at", and "t"). // Based on 'all-substrings-index-strategy' from js-search; // github.com/bvaughn/js-search/blob/master/source/index-strategy/all-substrings-index-strategy.ts ALL_SUBSTRINGS: "ALL_SUBSTRINGS", // Indexes for exact word matches only. // Based on 'exact-word-index-strategy' from js-search; // github.com/bvaughn/js-search/blob/master/source/index-strategy/exact-word-index-strategy.ts EXACT_WORDS: "EXACT_WORDS", // Indexes for prefix searches (e.g. the term "cat" is indexed as "c", "ca", and "cat" allowing prefix search lookups). // Based on 'prefix-index-strategy' from js-search; // github.com/bvaughn/js-search/blob/master/source/index-strategy/prefix-index-strategy.ts PREFIXES: "PREFIXES" }; /***/ }, /* 4 */ /***/ function(module, exports) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); 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"); } } /** * Maps search tokens to uids using a trie structure. */ var SearchIndex = function () { function SearchIndex() { _classCallCheck(this, SearchIndex); this.tokenToUidMap = {}; } /** * Maps the specified token to a uid. * * @param token Searchable token (e.g. "road") * @param uid Identifies a document within the searchable corpus */ _createClass(SearchIndex, [{ key: "indexDocument", value: function indexDocument(token, uid) { if (!this.tokenToUidMap[token]) { this.tokenToUidMap[token] = {}; } this.tokenToUidMap[token][uid] = uid; } /** * Finds uids that have been mapped to the set of tokens specified. * Only uids that have been mapped to all tokens will be returned. * * @param tokens Array of searchable tokens (e.g. ["long", "road"]) * @param matchAnyToken Whether to match any token. Default is false. * @return Array of uids that have been associated with the set of search tokens */ }, { key: "search", value: function search(tokens, matchAnyToken) { var _this = this; var uidMap = {}; var uidMatches = {}; var initialized = false; tokens.forEach(function (token) { var currentUidMap = _this.tokenToUidMap[token] || {}; if (!initialized) { initialized = true; for (var _uid in currentUidMap) { uidMap[_uid] = currentUidMap[_uid]; uidMatches[_uid] = 1; } } else { // Delete existing matches if using and AND query (the default) // Otherwise add new results to the matches if (!matchAnyToken) { for (var _uid2 in uidMap) { if (!currentUidMap[_uid2]) { delete uidMap[_uid2]; } } } else { for (var _uid3 in currentUidMap) { uidMap[_uid3] = currentUidMap[_uid3]; uidMatches[_uid3] = (uidMatches[_uid3] || 0) + 1; } } } }); var uids = []; for (var _uid4 in uidMap) { uids.push(uidMap[_uid4]); } // Sort according to most matches, if match any token is set. if (matchAnyToken) { uids.sort(function (a, b) { return uidMatches[b] - uidMatches[a]; }); } return uids; } }]); return SearchIndex; }(); exports.default = SearchIndex; /***/ } /******/ ]);