| Index: dashboard/dashboard/static/fuzzy_autocomplete.html
|
| diff --git a/dashboard/dashboard/static/fuzzy_autocomplete.html b/dashboard/dashboard/static/fuzzy_autocomplete.html
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..60de8905e1b3de9165cf67acdf33aafef98110fa
|
| --- /dev/null
|
| +++ b/dashboard/dashboard/static/fuzzy_autocomplete.html
|
| @@ -0,0 +1,81 @@
|
| +<!DOCTYPE html>
|
| +<!--
|
| +Copyright 2017 The Chromium Authors. All rights reserved.
|
| +Use of this source code is governed by a BSD-style license that can be
|
| +found in the LICENSE file.
|
| +-->
|
| +
|
| +<script>
|
| +'use strict';
|
| +
|
| +/** Support for fuzzy autocomplete suggestions. */
|
| +var fuzzyAutocomplete = (function() {
|
| + var FuzzyAutocomplete = function(sourceList) {
|
| + this.sourceList_ = sourceList;
|
| + this.headItems_ = sourceList.filter(item => item.head);
|
| + this.items_ = sourceList.filter(item => !item.head);
|
| + };
|
| +
|
| + FuzzyAutocomplete.prototype.search = function(target) {
|
| + if (target == null || target == undefined) {
|
| + return this.sourceList_;
|
| + }
|
| + var selector = new FuzzySelect(target, item => item.name);
|
| + var items = selector.filter(this.items_);
|
| + var groups = new Set(items.map(item => item.group));
|
| + var headItems = this.headItems_.filter(item => groups.has(item.name));
|
| + var results = [].concat(headItems).concat(items);
|
| +
|
| + // We piggyback lexical sorting. We want to sort first by group name then
|
| + // by is/is not group header then by score and then finally by name.
|
| + var toKey = function(item) {
|
| + if (item.head) {
|
| + // The score doesn't matter for head items.
|
| + return [item.name, false, 0, item.name];
|
| + }
|
| + return [item.group || '', true, selector.score(item), item.name];
|
| + };
|
| +
|
| + return results.map(item => [toKey(item), item]).sort().map(pair => pair[1]);
|
| + };
|
| +
|
| + /**
|
| + * FuzzySelect tests items to see if they match a query.
|
| + * An item matches a query if all space seperated parts from the query are
|
| + * present in the item.
|
| + *
|
| + * The optional accessor function can be provided if the items are not plain
|
| + * strings. It is passed an item and should return a string for use in
|
| + * testing against the query.
|
| + */
|
| + var FuzzySelect = function(query, opt_accessor) {
|
| + this.accessor = opt_accessor || (s => s);
|
| + query = query.replace(new RegExp(' +'), ' ');
|
| + this.rs = query.split(' ').map(part => new RegExp(part));
|
| + };
|
| +
|
| + FuzzySelect.prototype.filter = function(items) {
|
| + return items.filter(item => this.match(item));
|
| + };
|
| +
|
| + FuzzySelect.prototype.match = function(item) {
|
| + return this.rs.every(r => r.exec(this.accessor(item)));
|
| + };
|
| +
|
| + FuzzySelect.prototype.score = function(item) {
|
| + var score = 0; // Smaller is better.
|
| + for (var r of this.rs) {
|
| + var m = r.exec(this.accessor(item));
|
| + if (!m) return Number.POSITIVE_INFINITY;
|
| + score += m.index;
|
| + }
|
| + return score;
|
| + };
|
| +
|
| + return {
|
| + FuzzyAutocomplete,
|
| + FuzzySelect,
|
| + };
|
| +})();
|
| +
|
| +</script>
|
|
|