diff --git a/package.json b/package.json index d3a9ddd12f..6e011ea1be 100644 --- a/package.json +++ b/package.json @@ -136,7 +136,9 @@ "access": "public" }, "dependencies": { + "flexsearch": "^0.8.212", "highcharts": "12.1.2", + "minisearch": "^7.2.0", "pym.js": "1.3.2" } } diff --git a/src/components/autosuggest/autosuggest.ui.js b/src/components/autosuggest/autosuggest.ui.js index 871f5662ac..7e163767a6 100644 --- a/src/components/autosuggest/autosuggest.ui.js +++ b/src/components/autosuggest/autosuggest.ui.js @@ -1,6 +1,6 @@ import abortableFetch from '../../js/abortable-fetch'; import { sanitiseAutosuggestText } from './autosuggest.helpers'; -import runFuse from './fuse-config'; +import runFlexSearchIndex from './fuse-config'; import DOMPurify from 'dompurify'; export const baseClass = 'ons-js-autosuggest'; @@ -297,30 +297,23 @@ export default class AutosuggestUI { async fetchSuggestions(sanitisedQuery, data) { this.abortFetch(); - const threshold = - this.customResultsThreshold != null && this.customResultsThreshold >= 0 && this.customResultsThreshold <= 1 - ? this.customResultsThreshold - : 0.2; - - let distance; - if (threshold >= 0.6) { - distance = 500; - } else if (threshold >= 0.4) { - distance = 300; - } else { - distance = 100; - } - - const results = await runFuse(sanitisedQuery, data, this.lang, threshold, distance); + // Swap Fuse → FlexSearch + const results = await runFlexSearchIndex( + sanitisedQuery, + data, + this.lang, // searchField + ); results.forEach((result) => { - const resultItem = result.item ?? result; - - result.sanitisedText = sanitiseAutosuggestText( - resultItem[this.lang] ?? resultItem['formattedAddress'], - this.sanitisedQueryReplaceChars, - ); + result.sanitisedText = sanitiseAutosuggestText(result[this.lang] ?? result.formattedAddress, this.sanitisedQueryReplaceChars); }); + // results.forEach((result) => { + // result.sanitisedText = sanitiseAutosuggestText( + // result[this.lang] ?? result['formattedAddress'], + // this.sanitisedQueryReplaceChars, + // ); + // }); + return { status: this.responseStatus, results, diff --git a/src/components/autosuggest/fuse-config.js b/src/components/autosuggest/fuse-config.js index 70dade58b6..a60130eb7f 100644 --- a/src/components/autosuggest/fuse-config.js +++ b/src/components/autosuggest/fuse-config.js @@ -1,27 +1,27 @@ -import Fuse from 'fuse.js'; +import FlexSearch from 'flexsearch'; -export default function runFuse(query, data, searchFields, threshold, distance) { - const options = { - shouldSort: true, - threshold: threshold, - distance: distance, - keys: [ - { - name: searchFields, - weight: 0.9, - }, - { - name: 'formattedAddress', - weight: 0.9, - }, - { - name: 'tags', - weight: 0.1, - }, - ], - }; +export default function runFlexSearchIndex(query, data, searchField) { + const index = new FlexSearch.Index({ + preset: 'match', // enables raw substring scanning + tokenize: 'forward', // needed for substring matching + cache: true, + encode: false, + depth: 3, // improves substring matching window + }); - const fuse = new Fuse(data, options); - let result = fuse.search(query); - return result; + const documents = []; + + // Add documents + data.forEach((item, id) => { + const text = + (item[searchField] || '') + ' ' + (item.formattedAddress || '') + ' ' + (Array.isArray(item.tags) ? item.tags.join(' ') : ''); + + index.add(id, text); + documents[id] = item; + }); + + // Perform substring search + const resultIds = index.search(query); + + return resultIds.map((id) => documents[id]); } diff --git a/yarn.lock b/yarn.lock index e3b4927e4e..eddf1b51e8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6032,6 +6032,11 @@ flatted@^3.2.9: resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.9.tgz#7eb4c67ca1ba34232ca9d2d93e9886e611ad7daf" integrity sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ== +flexsearch@^0.8.212: + version "0.8.212" + resolved "https://registry.yarnpkg.com/flexsearch/-/flexsearch-0.8.212.tgz#b9509af778a991b938292e36fe0809a4ece4b940" + integrity sha512-wSyJr1GUWoOOIISRu+X2IXiOcVfg9qqBRyCPRUdLMIGJqPzMo+jMRlvE83t14v1j0dRMEaBbER/adQjp6Du2pw== + flush-write-stream@^1.0.2: version "1.1.1" resolved "https://registry.yarnpkg.com/flush-write-stream/-/flush-write-stream-1.1.1.tgz#8dd7d873a1babc207d94ead0c2e0e44276ebf2e8" @@ -9465,6 +9470,11 @@ minimist@^1.0.0, minimist@^1.1.0, minimist@^1.2.6, minimist@^1.2.8: resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.0.4.tgz#dbce03740f50a4786ba994c1fb908844d27b038c" integrity sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ== +minisearch@^7.2.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/minisearch/-/minisearch-7.2.0.tgz#3dc30e41e9464b3836553b6d969b656614f8f359" + integrity sha512-dqT2XBYUOZOiC5t2HRnwADjhNS2cecp9u+TJRiJ1Qp/f5qjkeT5APcGPjHw+bz89Ms8Jp+cG4AlE+QZ/QnDglg== + mitt@3.0.1, mitt@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/mitt/-/mitt-3.0.1.tgz#ea36cf0cc30403601ae074c8f77b7092cdab36d1"