From 235fc69280a18a5805d8eb84d76ada91ba49fe67 Mon Sep 17 00:00:00 2001 From: "Francois (fge)" Date: Wed, 28 Jul 2021 13:43:33 +0000 Subject: [PATCH] [IMP] web: replace fuzzy lib --- addons/web/__manifest__.py | 1 - .../web/static/lib/fuzzy-master/LICENCE-MIT | 22 --- addons/web/static/lib/fuzzy-master/fuzzy.js | 150 ------------------ addons/web/static/src/core/utils/search.js | 59 +++++++ .../src/legacy/js/control_panel/search_bar.js | 3 +- .../legacy/js/widgets/model_field_selector.js | 6 +- 6 files changed, 63 insertions(+), 178 deletions(-) delete mode 100644 addons/web/static/lib/fuzzy-master/LICENCE-MIT delete mode 100644 addons/web/static/lib/fuzzy-master/fuzzy.js create mode 100644 addons/web/static/src/core/utils/search.js diff --git a/addons/web/__manifest__.py b/addons/web/__manifest__.py index 95e0a7c7429..2ea2ea74fbe 100644 --- a/addons/web/__manifest__.py +++ b/addons/web/__manifest__.py @@ -96,7 +96,6 @@ This module provides the core of the Odoo Web Client. 'web/static/src/env.js', 'web/static/lib/jquery.scrollTo/jquery.scrollTo.js', - 'web/static/lib/fuzzy-master/fuzzy.js', 'web/static/lib/luxon/luxon.js', 'web/static/lib/py.js/lib/py.js', 'web/static/lib/py.js/lib/py_extras.js', diff --git a/addons/web/static/lib/fuzzy-master/LICENCE-MIT b/addons/web/static/lib/fuzzy-master/LICENCE-MIT deleted file mode 100644 index 8967579e739..00000000000 --- a/addons/web/static/lib/fuzzy-master/LICENCE-MIT +++ /dev/null @@ -1,22 +0,0 @@ -Copyright (c) 2012 Matt York - -Permission is hereby granted, free of charge, to any person -obtaining a copy of this software and associated documentation -files (the "Software"), to deal in the Software without -restriction, including without limitation the rights to use, -copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the -Software is furnished to do so, subject to the following -conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -OTHER DEALINGS IN THE SOFTWARE. diff --git a/addons/web/static/lib/fuzzy-master/fuzzy.js b/addons/web/static/lib/fuzzy-master/fuzzy.js deleted file mode 100644 index bdaf6af5adf..00000000000 --- a/addons/web/static/lib/fuzzy-master/fuzzy.js +++ /dev/null @@ -1,150 +0,0 @@ -/* - * Fuzzy - * https://github.com/myork/fuzzy - * - * Copyright (c) 2012 Matt York - * Licensed under the MIT license. - */ - -(function() { - -var root = this; - -var fuzzy = {}; - -// Use in node or in browser -if (typeof exports !== 'undefined') { - module.exports = fuzzy; -} else { - root.fuzzy = fuzzy; -} - -// Return all elements of `array` that have a fuzzy -// match against `pattern`. -fuzzy.simpleFilter = function(pattern, array) { - return array.filter(function(string) { - return fuzzy.test(pattern, string); - }); -}; - -// Does `pattern` fuzzy match `string`? -fuzzy.test = function(pattern, string) { - return fuzzy.match(pattern, string) !== null; -}; - -// If `pattern` matches `string`, wrap each matching character -// in `opts.pre` and `opts.post`. If no match, return null -fuzzy.match = function(pattern, string, opts) { - opts = opts || {}; - var patternIdx = 0 - , result = [] - , len = string.length - , totalScore = 0 - , currScore = 0 - // prefix - , pre = opts.pre || '' - // suffix - , post = opts.post || '' - // String to compare against. This might be a lowercase version of the - // raw string - , compareString = opts.caseSensitive && string || string.toLowerCase() - , ch, compareChar; - - pattern = opts.caseSensitive && pattern || pattern.toLowerCase(); - - // For each character in the string, either add it to the result - // or wrap in template if it's the next string in the pattern - for(var idx = 0; idx < len; idx++) { - ch = string[idx]; - if(compareString[idx] === pattern[patternIdx]) { - if (pattern[patternIdx] === ' ') { - // we don't want a space character to accumulate a larger score - currScore = 1 - idx/200; - } else { - // consecutive characters should increase the score more than linearly - currScore += 1 + currScore - idx/200; - } - - ch = pre + ch + post; - patternIdx += 1; - } else { - currScore = 0; - } - totalScore += currScore; - if (compareString[idx] === ' ') { - // we don't want characters after a space to accumulate a larger score - currScore = 0; - } - result[result.length] = ch; - } - - // return rendered string if we have a match for every char - if(patternIdx === pattern.length) { - return {rendered: result.join(''), score: totalScore}; - } - - return null; -}; - -// The normal entry point. Filters `arr` for matches against `pattern`. -// It returns an array with matching values of the type: -// -// [{ -// string: 'lah' // The rendered string -// , index: 2 // The index of the element in `arr` -// , original: 'blah' // The original element in `arr` -// }] -// -// `opts` is an optional argument bag. Details: -// -// opts = { -// // string to put before a matching character -// pre: '' -// -// // string to put after matching character -// , post: '' -// -// // Optional function. Input is an entry in the given arr`, -// // output should be the string to test `pattern` against. -// // In this example, if `arr = [{crying: 'koala'}]` we would return -// // 'koala'. -// , extract: function(arg) { return arg.crying; } -// } -fuzzy.filter = function(pattern, arr, opts) { - if(!arr || arr.length === 0) { - return [] - } - if (typeof pattern !== 'string') { - return arr - } - opts = opts || {}; - return arr - .reduce(function(prev, element, idx, arr) { - var str = element; - if(opts.extract) { - str = opts.extract(element); - } - var rendered = fuzzy.match(pattern, str, opts); - if(rendered != null) { - prev[prev.length] = { - string: rendered.rendered - , score: rendered.score - , index: idx - , original: element - }; - } - return prev; - }, []) - - // Sort by score. Browsers are inconsistent wrt stable/unstable - // sorting, so force stable by using the index in the case of tie. - // See http://ofb.net/~sethml/is-sort-stable.html - .sort(function(a,b) { - var compare = b.score - a.score; - if(compare) return compare; - return a.index - b.index; - }); -}; - - -}()); diff --git a/addons/web/static/src/core/utils/search.js b/addons/web/static/src/core/utils/search.js new file mode 100644 index 00000000000..e992f547485 --- /dev/null +++ b/addons/web/static/src/core/utils/search.js @@ -0,0 +1,59 @@ +/** @odoo-module */ + +/** + * This private function computes a score that represent the fact that the + * string contains the pattern, or not + * + * - If the score is 0, the string does not contain the letters of the pattern in + * the correct order. + * - if the score is > 0, it actually contains the letters. + * + * Better matches will get a higher score: consecutive letters are better, + * and a match closer to the beginning of the string is also scored higher. + */ +function match(pattern, str) { + let totalScore = 0; + let currentScore = 0; + let len = str.length; + let patternIndex = 0; + + pattern = pattern.toLowerCase(); + str = str.toLowerCase(); + + for (let i = 0; i < len; i++) { + if (str[i] === pattern[patternIndex]) { + patternIndex++; + currentScore += 100 + currentScore - i / 200; + } else { + currentScore = 0; + } + totalScore = totalScore + currentScore; + } + + return patternIndex === pattern.length ? totalScore : 0; +} + +/** + * Return a list of things that matches a pattern, ordered by their 'score' ( + * higher score first). An higher score means that the match is better. For + * example, consecutive letters are considered a better match. + */ +export function fuzzyLookup(pattern, list, fn) { + const results = []; + list.forEach((data) => { + const score = match(pattern, fn(data)); + if (score > 0) { + results.push({ score, elem: data }); + } + }); + + // we want better matches first + results.sort((a, b) => b.score - a.score); + + return results.map((r) => r.elem); +} + +// Does `pattern` fuzzy match `string`? +export function fuzzyTest(pattern, string) { + return match(pattern, string) !== 0; +} diff --git a/addons/web/static/src/legacy/js/control_panel/search_bar.js b/addons/web/static/src/legacy/js/control_panel/search_bar.js index 50f3e7b6ad9..f4bd2e04e0d 100644 --- a/addons/web/static/src/legacy/js/control_panel/search_bar.js +++ b/addons/web/static/src/legacy/js/control_panel/search_bar.js @@ -5,6 +5,7 @@ odoo.define('web.SearchBar', function (require) { const field_utils = require('web.field_utils'); const { useAutofocus } = require('web.custom_hooks'); const { useModel } = require('web.Model'); + const { fuzzyTest } = require('@web/core/utils/search'); const CHAR_FIELDS = ['char', 'html', 'many2many', 'many2one', 'one2many', 'text']; const { Component, hooks } = owl; @@ -199,7 +200,7 @@ odoo.define('web.SearchBar', function (require) { if (source.selection) { const options = []; source.selection.forEach(result => { - if (fuzzy.test(query, result[1].toLowerCase())) { + if (fuzzyTest(query, result[1].toLowerCase())) { options.push(this._createSubSource(source, result)); } }); diff --git a/addons/web/static/src/legacy/js/widgets/model_field_selector.js b/addons/web/static/src/legacy/js/widgets/model_field_selector.js index c2a2c040be9..e8dc2515ad9 100644 --- a/addons/web/static/src/legacy/js/widgets/model_field_selector.js +++ b/addons/web/static/src/legacy/js/widgets/model_field_selector.js @@ -3,6 +3,7 @@ odoo.define("web.ModelFieldSelector", function (require) { var core = require("web.core"); var Widget = require("web.Widget"); +const { fuzzyLookup } = require('@web/core/utils/search'); var _t = core._t; @@ -381,10 +382,7 @@ var ModelFieldSelector = Widget.extend({ var lines = _.filter(page, this.options.filter); if (this.searchValue) { - var matches = fuzzy.filter(this.searchValue, _.pluck(lines, 'string')); - lines = _.map(_.pluck(matches, 'index'), function (i) { - return lines[i]; - }); + lines = fuzzyLookup(this.searchValue, lines, (l) => l.string); } this.$(".o_field_selector_page").replaceWith(core.qweb.render(this.template + ".page", {