Merge pull request #4977 from matrix-org/t3chguy/query-matcher-unhomoglyph

Query Matcher use unhomoglyph for a little bit more leniency
This commit is contained in:
Michael Telatynski 2020-07-20 15:35:47 +01:00 committed by GitHub
commit 44043d6dd5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -18,16 +18,15 @@ limitations under the License.
import _at from 'lodash/at'; import _at from 'lodash/at';
import _uniq from 'lodash/uniq'; import _uniq from 'lodash/uniq';
import {removeHiddenChars} from "matrix-js-sdk/src/utils";
function stripDiacritics(str: string): string {
return str.normalize('NFD').replace(/[\u0300-\u036f]/g, '');
}
interface IOptions<T extends {}> { interface IOptions<T extends {}> {
keys: Array<string | keyof T>; keys: Array<string | keyof T>;
funcs?: Array<(T) => string>; funcs?: Array<(T) => string>;
shouldMatchWordsOnly?: boolean; shouldMatchWordsOnly?: boolean;
shouldMatchPrefix?: boolean; shouldMatchPrefix?: boolean;
// whether to apply unhomoglyph and strip diacritics to fuzz up the search. Defaults to true
fuzzy?: boolean;
} }
/** /**
@ -46,14 +45,10 @@ interface IOptions<T extends {}> {
*/ */
export default class QueryMatcher<T extends Object> { export default class QueryMatcher<T extends Object> {
private _options: IOptions<T>; private _options: IOptions<T>;
private _keys: IOptions<T>["keys"];
private _funcs: Required<IOptions<T>["funcs"]>;
private _items: Map<string, {object: T, keyWeight: number}[]>; private _items: Map<string, {object: T, keyWeight: number}[]>;
constructor(objects: T[], options: IOptions<T> = { keys: [] }) { constructor(objects: T[], options: IOptions<T> = { keys: [] }) {
this._options = options; this._options = options;
this._keys = options.keys;
this._funcs = options.funcs || [];
this.setObjects(objects); this.setObjects(objects);
@ -78,15 +73,17 @@ export default class QueryMatcher<T extends Object> {
// type for their values. We assume that those values who's keys have // type for their values. We assume that those values who's keys have
// been specified will be string. Also, we cannot infer all the // been specified will be string. Also, we cannot infer all the
// types of the keys of the objects at compile. // types of the keys of the objects at compile.
const keyValues = _at<string>(<any>object, this._keys); const keyValues = _at<string>(<any>object, this._options.keys);
for (const f of this._funcs) { if (this._options.funcs) {
keyValues.push(f(object)); for (const f of this._options.funcs) {
keyValues.push(f(object));
}
} }
for (const [index, keyValue] of Object.entries(keyValues)) { for (const [index, keyValue] of Object.entries(keyValues)) {
if (!keyValue) continue; // skip falsy keyValues if (!keyValue) continue; // skip falsy keyValues
const key = stripDiacritics(keyValue).toLowerCase(); const key = this.processQuery(keyValue);
if (!this._items.has(key)) { if (!this._items.has(key)) {
this._items.set(key, []); this._items.set(key, []);
} }
@ -99,7 +96,7 @@ export default class QueryMatcher<T extends Object> {
} }
match(query: string): T[] { match(query: string): T[] {
query = stripDiacritics(query).toLowerCase(); query = this.processQuery(query);
if (this._options.shouldMatchWordsOnly) { if (this._options.shouldMatchWordsOnly) {
query = query.replace(/[^\w]/g, ''); query = query.replace(/[^\w]/g, '');
} }
@ -142,4 +139,11 @@ export default class QueryMatcher<T extends Object> {
// Now map the keys to the result objects. Also remove any duplicates. // Now map the keys to the result objects. Also remove any duplicates.
return _uniq(matches.map((match) => match.object)); return _uniq(matches.map((match) => match.object));
} }
private processQuery(query: string): string {
if (this._options.fuzzy !== false) {
return removeHiddenChars(query).toLowerCase();
}
return query.toLowerCase();
}
} }