From b3ad282f400bbaffbb4cf60b0b4e059ab8279a84 Mon Sep 17 00:00:00 2001 From: Quincy Morgan <2046746+quincylvania@users.noreply.github.com> Date: Wed, 9 Dec 2020 17:12:43 -0500 Subject: [PATCH] Ignore diacritics when searching presets (close #8242) --- modules/presets/category.js | 11 ++++++++ modules/presets/collection.js | 52 +++++++++++++++++++---------------- modules/presets/preset.js | 11 ++++++++ 3 files changed, 50 insertions(+), 24 deletions(-) diff --git a/modules/presets/category.js b/modules/presets/category.js index 7a2fe789f..4843e923a 100644 --- a/modules/presets/category.js +++ b/modules/presets/category.js @@ -8,6 +8,7 @@ import { presetCollection } from './collection'; // export function presetCategory(categoryID, category, all) { let _this = Object.assign({}, category); // shallow copy + let _searchName; // cache _this.id = categoryID; @@ -38,6 +39,16 @@ export function presetCategory(categoryID, category, all) { _this.terms = () => []; + _this.searchName = () => { + if (!_searchName) { + _searchName = (_this.suggestion ? _this.originalName : _this.name()).toLowerCase(); + // split combined diacritical characters into their parts + if (_searchName.normalize) _searchName = _searchName.normalize('NFD'); + // remove diacritics + _searchName = _searchName.replace(/[\u0300-\u036f]/g, ''); + } + return _searchName; + }; return _this; } diff --git a/modules/presets/collection.js b/modules/presets/collection.js index c1d09e325..7ddc72d18 100644 --- a/modules/presets/collection.js +++ b/modules/presets/collection.js @@ -49,6 +49,10 @@ export function presetCollection(collection) { if (!value) return _this; value = value.toLowerCase().trim(); + // split combined diacritical characters into their parts + if (value.normalize) value = value.normalize('NFD'); + // remove diacritics + value = value.replace(/[\u0300-\u036f]/g, ''); // match at name beginning or just after a space (e.g. "office" -> match "Law Office") function leading(a) { @@ -63,8 +67,8 @@ export function presetCollection(collection) { } function sortNames(a, b) { - let aCompare = (a.suggestion ? a.originalName : a.name()).toLowerCase(); - let bCompare = (b.suggestion ? b.originalName : b.name()).toLowerCase(); + let aCompare = a.searchName(); + let bCompare = b.searchName(); // priority if search string matches preset name exactly - #4325 if (value === aCompare) return -1; @@ -96,52 +100,52 @@ export function presetCollection(collection) { const suggestions = pool.filter(a => a.suggestion === true); // matches value to preset.name - const leading_name = searchable - .filter(a => leading(a.name().toLowerCase())) + const leadingName = searchable + .filter(a => leading(a.searchName())) .sort(sortNames); - // matches value to preset suggestion name (original name is unhyphenated) - const leading_suggestions = suggestions - .filter(a => leadingStrict(a.originalName.toLowerCase())) + // matches value to preset suggestion name + const leadingSuggestions = suggestions + .filter(a => leadingStrict(a.searchName())) .sort(sortNames); // matches value to preset.terms values - const leading_terms = searchable + const leadingTerms = searchable .filter(a => (a.terms() || []).some(leading)); // matches value to preset.tags values - const leading_tag_values = searchable + const leadingTagValues = searchable .filter(a => Object.values(a.tags || {}).filter(val => val !== '*').some(leading)); // finds close matches to value in preset.name - const similar_name = searchable - .map(a => ({ preset: a, dist: utilEditDistance(value, a.name()) })) - .filter(a => a.dist + Math.min(value.length - a.preset.name().length, 0) < 3) + const similarName = searchable + .map(a => ({ preset: a, dist: utilEditDistance(value, a.searchName()) })) + .filter(a => a.dist + Math.min(value.length - a.preset.searchName().length, 0) < 3) .sort((a, b) => a.dist - b.dist) .map(a => a.preset); - // finds close matches to value to preset suggestion name (original name is unhyphenated) - const similar_suggestions = suggestions - .map(a => ({ preset: a, dist: utilEditDistance(value, a.originalName.toLowerCase()) })) - .filter(a => a.dist + Math.min(value.length - a.preset.originalName.length, 0) < 1) + // finds close matches to value to preset suggestion name + const similarSuggestions = suggestions + .map(a => ({ preset: a, dist: utilEditDistance(value, a.searchName()) })) + .filter(a => a.dist + Math.min(value.length - a.preset.searchName().length, 0) < 1) .sort((a, b) => a.dist - b.dist) .map(a => a.preset); // finds close matches to value in preset.terms - const similar_terms = searchable + const similarTerms = searchable .filter(a => { return (a.terms() || []).some(b => { return utilEditDistance(value, b) + Math.min(value.length - b.length, 0) < 3; }); }); - let results = leading_name.concat( - leading_suggestions, - leading_terms, - leading_tag_values, - similar_name, - similar_suggestions, - similar_terms + let results = leadingName.concat( + leadingSuggestions, + leadingTerms, + leadingTagValues, + similarName, + similarSuggestions, + similarTerms ).slice(0, MAXRESULTS - 1); if (geometry) { diff --git a/modules/presets/preset.js b/modules/presets/preset.js index 9e81415cc..85253cf78 100644 --- a/modules/presets/preset.js +++ b/modules/presets/preset.js @@ -15,6 +15,7 @@ export function presetPreset(presetID, preset, addable, allFields, allPresets) { let _addable = addable || false; let _resolvedFields; // cache let _resolvedMoreFields; // cache + let _searchName; // cache _this.id = presetID; @@ -120,6 +121,16 @@ export function presetPreset(presetID, preset, addable, allFields, allPresets) { _this.terms = () => _this.t('terms', { 'default': _this.originalTerms }) .toLowerCase().trim().split(/\s*,+\s*/); + _this.searchName = () => { + if (!_searchName) { + _searchName = (_this.suggestion ? _this.originalName : _this.name()).toLowerCase(); + // split combined diacritical characters into their parts + if (_searchName.normalize) _searchName = _searchName.normalize('NFD'); + // remove diacritics + _searchName = _searchName.replace(/[\u0300-\u036f]/g, ''); + } + return _searchName; + }; _this.isFallback = () => { const tagCount = Object.keys(_this.tags).length;