diff --git a/app/assets/javascripts/spotlight/spotlight.esm.js b/app/assets/javascripts/spotlight/spotlight.esm.js
index e8038f9c7..bd7e56ba6 100644
--- a/app/assets/javascripts/spotlight/spotlight.esm.js
+++ b/app/assets/javascripts/spotlight/spotlight.esm.js
@@ -905,535 +905,6 @@ Downcoder.Initialize = function()
Downcoder.regex = new RegExp('[' + Downcoder.chars + ']|[^' + Downcoder.chars + ']+','g') ;
};
-/* From https://github.com/TimSchlechter/bootstrap-tagsinput/blob/2661784c2c281d3a69b93897ff3f39e4ffa5cbd1/dist/bootstrap-tagsinput.js */
-
-/* The MIT License (MIT)
-
-Copyright (c) 2013 Tim Schlechter
-
-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.
-*/
-
-/* Retrieved 12 February 2014 */
-
-(function ($) {
-
- var defaultOptions = {
- tagClass: function(item) {
- return 'badge badge-info';
- },
- itemValue: function(item) {
- return item ? item.toString() : item;
- },
- itemText: function(item) {
- return this.itemValue(item);
- },
- freeInput: true,
- maxTags: undefined,
- confirmKeys: [13],
- onTagExists: function(item, $tag) {
- $tag.hide().fadeIn();
- }
- };
-
- /**
- * Constructor function
- */
- function TagsInput(element, options) {
- this.itemsArray = [];
-
- this.$element = $(element);
- this.$element.hide();
-
- this.isSelect = (element.tagName === 'SELECT');
- this.multiple = (this.isSelect && element.hasAttribute('multiple'));
- this.objectItems = options && options.itemValue;
- this.placeholderText = element.hasAttribute('placeholder') ? this.$element.attr('placeholder') : '';
- this.inputSize = Math.max(1, this.placeholderText.length);
-
- this.$container = $('
');
- this.$input = $('').appendTo(this.$container);
-
- this.$element.after(this.$container);
-
- this.build(options);
- }
-
- TagsInput.prototype = {
- constructor: TagsInput,
-
- /**
- * Adds the given item as a new tag. Pass true to dontPushVal to prevent
- * updating the elements val()
- */
- add: function(item, dontPushVal) {
- var self = this;
-
- if (self.options.maxTags && self.itemsArray.length >= self.options.maxTags)
- return;
-
- // Ignore falsey values, except false
- if (item !== false && !item)
- return;
-
- // Throw an error when trying to add an object while the itemValue option was not set
- if (typeof item === "object" && !self.objectItems)
- throw("Can't add objects when itemValue option is not set");
-
- // Ignore strings only containg whitespace
- if (item.toString().match(/^\s*$/))
- return;
-
- // If SELECT but not multiple, remove current tag
- if (self.isSelect && !self.multiple && self.itemsArray.length > 0)
- self.remove(self.itemsArray[0]);
-
- if (typeof item === "string" && this.$element[0].tagName === 'INPUT') {
- var items = item.split(',');
- if (items.length > 1) {
- for (var i = 0; i < items.length; i++) {
- this.add(items[i], true);
- }
-
- if (!dontPushVal)
- self.pushVal();
- return;
- }
- }
-
- var itemValue = self.options.itemValue(item),
- itemText = self.options.itemText(item),
- tagClass = self.options.tagClass(item);
-
- // Ignore items allready added
- var existing = $.grep(self.itemsArray, function(item) { return self.options.itemValue(item) === itemValue; } )[0];
- if (existing) {
- // Invoke onTagExists
- if (self.options.onTagExists) {
- var $existingTag = $(".tag", self.$container).filter(function() { return $(this).data("item") === existing; });
- self.options.onTagExists(item, $existingTag);
- }
- return;
- }
-
- // register item in internal array and map
- self.itemsArray.push(item);
-
- // add a tag element
- var $tag = $('' + htmlEncode(itemText) + '');
- $tag.data('item', item);
- self.findInputWrapper().before($tag);
- $tag.after(' ');
-
- // add if item represents a value not present in one of the 's options
- if (self.isSelect && !$('option[value="' + escape(itemValue) + '"]',self.$element)[0]) {
- var $option = $('');
- $option.data('item', item);
- $option.attr('value', itemValue);
- self.$element.append($option);
- }
-
- if (!dontPushVal)
- self.pushVal();
-
- // Add class when reached maxTags
- if (self.options.maxTags === self.itemsArray.length)
- self.$container.addClass('bootstrap-tagsinput-max');
-
- self.$element.trigger($.Event('itemAdded', { item: item }));
- },
-
- /**
- * Removes the given item. Pass true to dontPushVal to prevent updating the
- * elements val()
- */
- remove: function(item, dontPushVal) {
- var self = this;
-
- if (self.objectItems) {
- if (typeof item === "object")
- item = $.grep(self.itemsArray, function(other) { return self.options.itemValue(other) == self.options.itemValue(item); } )[0];
- else
- item = $.grep(self.itemsArray, function(other) { return self.options.itemValue(other) == item; } )[0];
- }
-
- if (item) {
- $('.tag', self.$container).filter(function() { return $(this).data('item') === item; }).remove();
- $('option', self.$element).filter(function() { return $(this).data('item') === item; }).remove();
- self.itemsArray.splice($.inArray(item, self.itemsArray), 1);
- }
-
- if (!dontPushVal)
- self.pushVal();
-
- // Remove class when reached maxTags
- if (self.options.maxTags > self.itemsArray.length)
- self.$container.removeClass('bootstrap-tagsinput-max');
-
- self.$element.trigger($.Event('itemRemoved', { item: item }));
- },
-
- /**
- * Removes all items
- */
- removeAll: function() {
- var self = this;
-
- $('.tag', self.$container).remove();
- $('option', self.$element).remove();
-
- while(self.itemsArray.length > 0)
- self.itemsArray.pop();
-
- self.pushVal();
-
- if (self.options.maxTags && !this.isEnabled())
- this.enable();
- },
-
- /**
- * Refreshes the tags so they match the text/value of their corresponding
- * item.
- */
- refresh: function() {
- var self = this;
- $('.tag', self.$container).each(function() {
- var $tag = $(this),
- item = $tag.data('item'),
- itemValue = self.options.itemValue(item),
- itemText = self.options.itemText(item),
- tagClass = self.options.tagClass(item);
-
- // Update tag's class and inner text
- $tag.attr('class', null);
- $tag.addClass('tag ' + htmlEncode(tagClass));
- $tag.contents().filter(function() {
- return this.nodeType == 3;
- })[0].nodeValue = htmlEncode(itemText);
-
- if (self.isSelect) {
- var option = $('option', self.$element).filter(function() { return $(this).data('item') === item; });
- option.attr('value', itemValue);
- }
- });
- },
-
- /**
- * Returns the items added as tags
- */
- items: function() {
- return this.itemsArray;
- },
-
- /**
- * Assembly value by retrieving the value of each item, and set it on the
- * element.
- */
- pushVal: function() {
- var self = this,
- val = $.map(self.items(), function(item) {
- return self.options.itemValue(item).toString();
- });
-
- self.$element.val(val, true).trigger('change');
- },
-
- /**
- * Initializes the tags input behaviour on the element
- */
- build: function(options) {
- var self = this;
-
- self.options = $.extend({}, defaultOptions, options);
- var typeahead = self.options.typeahead || {};
-
- // When itemValue is set, freeInput should always be false
- if (self.objectItems)
- self.options.freeInput = false;
-
- makeOptionItemFunction(self.options, 'itemValue');
- makeOptionItemFunction(self.options, 'itemText');
- makeOptionItemFunction(self.options, 'tagClass');
-
- // for backwards compatibility, self.options.source is deprecated
- if (self.options.source)
- typeahead.source = self.options.source;
-
- if (typeahead.source && $.fn.typeahead) {
- makeOptionFunction(typeahead, 'source');
-
- self.$input.typeahead({
- source: function (query, process) {
- function processItems(items) {
- var texts = [];
-
- for (var i = 0; i < items.length; i++) {
- var text = self.options.itemText(items[i]);
- map[text] = items[i];
- texts.push(text);
- }
- process(texts);
- }
-
- this.map = {};
- var map = this.map,
- data = typeahead.source(query);
-
- if ($.isFunction(data.success)) {
- // support for Angular promises
- data.success(processItems);
- } else {
- // support for functions and jquery promises
- $.when(data)
- .then(processItems);
- }
- },
- updater: function (text) {
- self.add(this.map[text]);
- },
- matcher: function (text) {
- return (text.toLowerCase().indexOf(this.query.trim().toLowerCase()) !== -1);
- },
- sorter: function (texts) {
- return texts.sort();
- },
- highlighter: function (text) {
- var regex = new RegExp( '(' + this.query + ')', 'gi' );
- return text.replace( regex, "$1" );
- }
- });
- }
-
- self.$container.on('click', $.proxy(function(event) {
- self.$input.focus();
- }, self));
-
- self.$container.on('keydown', 'input', $.proxy(function(event) {
- var $input = $(event.target),
- $inputWrapper = self.findInputWrapper();
-
- switch (event.which) {
- // BACKSPACE
- case 8:
- if (doGetCaretPosition($input[0]) === 0) {
- var prev = $inputWrapper.prev();
- if (prev) {
- self.remove(prev.data('item'));
- }
- }
- break;
-
- // DELETE
- case 46:
- if (doGetCaretPosition($input[0]) === 0) {
- var next = $inputWrapper.next();
- if (next) {
- self.remove(next.data('item'));
- }
- }
- break;
-
- // LEFT ARROW
- case 37:
- // Try to move the input before the previous tag
- var $prevTag = $inputWrapper.prev();
- if ($input.val().length === 0 && $prevTag[0]) {
- $prevTag.before($inputWrapper);
- $input.focus();
- }
- break;
- // RIGHT ARROW
- case 39:
- // Try to move the input after the next tag
- var $nextTag = $inputWrapper.next();
- if ($input.val().length === 0 && $nextTag[0]) {
- $nextTag.after($inputWrapper);
- $input.focus();
- }
- break;
- default:
- // When key corresponds one of the confirmKeys, add current input
- // as a new tag
- if (self.options.freeInput && $.inArray(event.which, self.options.confirmKeys) >= 0) {
- self.add($input.val());
- $input.val('');
- event.preventDefault();
- }
- }
-
- // Reset internal input's size
- $input.attr('size', Math.max(this.inputSize, $input.val().length));
- }, self));
-
- // Remove icon clicked
- self.$container.on('click', '[data-role=remove]', $.proxy(function(event) {
- self.remove($(event.target).closest('.tag').data('item'));
- }, self));
-
- // Only add existing value as tags when using strings as tags
- if (self.options.itemValue === defaultOptions.itemValue) {
- if (self.$element[0].tagName === 'INPUT') {
- self.add(self.$element.val());
- } else {
- $('option', self.$element).each(function() {
- self.add($(this).attr('value'), true);
- });
- }
- }
- },
-
- /**
- * Removes all tagsinput behaviour and unregsiter all event handlers
- */
- destroy: function() {
- var self = this;
-
- // Unbind events
- self.$container.off('keypress', 'input');
- self.$container.off('click', '[role=remove]');
-
- self.$container.remove();
- self.$element.removeData('tagsinput');
- self.$element.show();
- },
-
- /**
- * Sets focus on the tagsinput
- */
- focus: function() {
- this.$input.focus();
- },
-
- /**
- * Returns the internal input element
- */
- input: function() {
- return this.$input;
- },
-
- /**
- * Returns the element which is wrapped around the internal input. This
- * is normally the $container, but typeahead.js moves the $input element.
- */
- findInputWrapper: function() {
- var elt = this.$input[0],
- container = this.$container[0];
- while(elt && elt.parentNode !== container)
- elt = elt.parentNode;
-
- return $(elt);
- }
- };
-
- /**
- * Register JQuery plugin
- */
- $.fn.tagsinput = function(arg1, arg2) {
- var results = [];
-
- this.each(function() {
- var tagsinput = $(this).data('tagsinput');
-
- // Initialize a new tags input
- if (!tagsinput) {
- tagsinput = new TagsInput(this, arg1);
- $(this).data('tagsinput', tagsinput);
- results.push(tagsinput);
-
- if (this.tagName === 'SELECT') {
- $('option', $(this)).attr('selected', 'selected');
- }
-
- // Init tags from $(this).val()
- $(this).val($(this).val());
- } else {
- // Invoke function on existing tags input
- var retVal = tagsinput[arg1](arg2);
- if (retVal !== undefined)
- results.push(retVal);
- }
- });
-
- if ( typeof arg1 == 'string') {
- // Return the results from the invoked function calls
- return results.length > 1 ? results : results[0];
- } else {
- return results;
- }
- };
-
- $.fn.tagsinput.Constructor = TagsInput;
-
- /**
- * Most options support both a string or number as well as a function as
- * option value. This function makes sure that the option with the given
- * key in the given options is wrapped in a function
- */
- function makeOptionItemFunction(options, key) {
- if (typeof options[key] !== 'function') {
- var propertyName = options[key];
- options[key] = function(item) { return item[propertyName]; };
- }
- }
- function makeOptionFunction(options, key) {
- if (typeof options[key] !== 'function') {
- var value = options[key];
- options[key] = function() { return value; };
- }
- }
- /**
- * HtmlEncodes the given value
- */
- var htmlEncodeContainer = $('');
- function htmlEncode(value) {
- if (value) {
- return htmlEncodeContainer.text(value).html();
- } else {
- return '';
- }
- }
-
- /**
- * Returns the position of the caret in the given input field
- * http://flightschool.acylt.com/devnotes/caret-position-woes/
- */
- function doGetCaretPosition(oField) {
- var iCaretPos = 0;
- if (document.selection) {
- oField.focus ();
- var oSel = document.selection.createRange();
- oSel.moveStart ('character', -oField.value.length);
- iCaretPos = oSel.text.length;
- } else if (oField.selectionStart || oField.selectionStart == '0') {
- iCaretPos = oField.selectionStart;
- }
- return (iCaretPos);
- }
-
- /**
- * Initialize tagsinput behaviour on inputs and selects which have
- * data-role=tagsinput
- */
- $(function() {
- $("input[data-role=tagsinput], select[multiple][data-role=tagsinput]").tagsinput();
- });
-})(window.jQuery);
-
/*!
SerializeJSON jQuery plugin.
https://github.com/marioizquierdo/jquery.serializeJSON
@@ -4728,42 +4199,5326 @@ class EditInPlace {
}
}
-class ExhibitTagAutocomplete {
- connect() {
- $('[data-autocomplete-tag="true"]').each(function(_i, el) {
- var $el = $(el);
- // By default tags input binds on page ready to [data-role=tagsinput],
- // however, that doesn't work with Turbolinks. So we init manually:
- $el.tagsinput();
-
- var tags = new Bloodhound({
- datumTokenizer: function(d) { return Bloodhound.tokenizers.whitespace(d.name); },
- queryTokenizer: Bloodhound.tokenizers.whitespace,
- limit: 100,
- prefetch: {
- url: $el.data('autocomplete-url'),
- ttl: 1,
- filter: function(list) {
- // Let the dom know that the response has been returned
- $el.attr('data-autocomplete-fetched', true);
- return $.map(list, function(tag) { return { name: tag }; });
+/**
+* Tom Select v2.3.1
+* Licensed under the Apache License, Version 2.0 (the "License");
+*/
+
+/**
+ * MicroEvent - to make any js object an event emitter
+ *
+ * - pure javascript - server compatible, browser compatible
+ * - dont rely on the browser doms
+ * - super simple - you get it immediatly, no mistery, no magic involved
+ *
+ * @author Jerome Etienne (https://github.com/jeromeetienne)
+ */
+
+/**
+ * Execute callback for each event in space separated list of event names
+ *
+ */
+function forEvents(events, callback) {
+ events.split(/\s+/).forEach(event => {
+ callback(event);
+ });
+}
+class MicroEvent {
+ constructor() {
+ this._events = void 0;
+ this._events = {};
+ }
+ on(events, fct) {
+ forEvents(events, event => {
+ const event_array = this._events[event] || [];
+ event_array.push(fct);
+ this._events[event] = event_array;
+ });
+ }
+ off(events, fct) {
+ var n = arguments.length;
+ if (n === 0) {
+ this._events = {};
+ return;
+ }
+ forEvents(events, event => {
+ if (n === 1) {
+ delete this._events[event];
+ return;
+ }
+ const event_array = this._events[event];
+ if (event_array === undefined) return;
+ event_array.splice(event_array.indexOf(fct), 1);
+ this._events[event] = event_array;
+ });
+ }
+ trigger(events, ...args) {
+ var self = this;
+ forEvents(events, event => {
+ const event_array = self._events[event];
+ if (event_array === undefined) return;
+ event_array.forEach(fct => {
+ fct.apply(self, args);
+ });
+ });
+ }
+}
+
+/**
+ * microplugin.js
+ * Copyright (c) 2013 Brian Reavis & contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this
+ * file except in compliance with the License. You may obtain a copy of the License at:
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
+ * ANY KIND, either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ *
+ * @author Brian Reavis
+ */
+
+function MicroPlugin(Interface) {
+ Interface.plugins = {};
+ return class extends Interface {
+ constructor(...args) {
+ super(...args);
+ this.plugins = {
+ names: [],
+ settings: {},
+ requested: {},
+ loaded: {}
+ };
+ }
+ /**
+ * Registers a plugin.
+ *
+ * @param {function} fn
+ */
+ static define(name, fn) {
+ Interface.plugins[name] = {
+ 'name': name,
+ 'fn': fn
+ };
+ }
+
+ /**
+ * Initializes the listed plugins (with options).
+ * Acceptable formats:
+ *
+ * List (without options):
+ * ['a', 'b', 'c']
+ *
+ * List (with options):
+ * [{'name': 'a', options: {}}, {'name': 'b', options: {}}]
+ *
+ * Hash (with options):
+ * {'a': { ... }, 'b': { ... }, 'c': { ... }}
+ *
+ * @param {array|object} plugins
+ */
+ initializePlugins(plugins) {
+ var key, name;
+ const self = this;
+ const queue = [];
+ if (Array.isArray(plugins)) {
+ plugins.forEach(plugin => {
+ if (typeof plugin === 'string') {
+ queue.push(plugin);
+ } else {
+ self.plugins.settings[plugin.name] = plugin.options;
+ queue.push(plugin.name);
+ }
+ });
+ } else if (plugins) {
+ for (key in plugins) {
+ if (plugins.hasOwnProperty(key)) {
+ self.plugins.settings[key] = plugins[key];
+ queue.push(key);
}
}
- });
+ }
+ while (name = queue.shift()) {
+ self.require(name);
+ }
+ }
+ loadPlugin(name) {
+ var self = this;
+ var plugins = self.plugins;
+ var plugin = Interface.plugins[name];
+ if (!Interface.plugins.hasOwnProperty(name)) {
+ throw new Error('Unable to find "' + name + '" plugin');
+ }
+ plugins.requested[name] = true;
+ plugins.loaded[name] = plugin.fn.apply(self, [self.plugins.settings[name] || {}]);
+ plugins.names.push(name);
+ }
- tags.initialize();
-
- $el.tagsinput('input').typeahead({highlight: true, hint: false}, {
- name: 'tags',
- displayKey: 'name',
- source: tags.ttAdapter()
- }).bind('typeahead:selected', $.proxy(function (obj, datum) {
- $el.tagsinput('add', datum.name);
- $el.tagsinput('input').typeahead('val', '');
- })).bind('blur', function() {
- $el.tagsinput('add', $el.tagsinput('input').typeahead('val'));
- $el.tagsinput('input').typeahead('val', '');
- });
+ /**
+ * Initializes a plugin.
+ *
+ */
+ require(name) {
+ var self = this;
+ var plugins = self.plugins;
+ if (!self.plugins.loaded.hasOwnProperty(name)) {
+ if (plugins.requested[name]) {
+ throw new Error('Plugin has circular dependency ("' + name + '")');
+ }
+ self.loadPlugin(name);
+ }
+ return plugins.loaded[name];
+ }
+ };
+}
+
+/*! @orchidjs/unicode-variants | https://github.com/orchidjs/unicode-variants | Apache License (v2) */
+/**
+ * Convert array of strings to a regular expression
+ * ex ['ab','a'] => (?:ab|a)
+ * ex ['a','b'] => [ab]
+ * @param {string[]} chars
+ * @return {string}
+ */
+const arrayToPattern = chars => {
+ chars = chars.filter(Boolean);
+
+ if (chars.length < 2) {
+ return chars[0] || '';
+ }
+
+ return maxValueLength(chars) == 1 ? '[' + chars.join('') + ']' : '(?:' + chars.join('|') + ')';
+};
+/**
+ * @param {string[]} array
+ * @return {string}
+ */
+
+const sequencePattern = array => {
+ if (!hasDuplicates(array)) {
+ return array.join('');
+ }
+
+ let pattern = '';
+ let prev_char_count = 0;
+
+ const prev_pattern = () => {
+ if (prev_char_count > 1) {
+ pattern += '{' + prev_char_count + '}';
+ }
+ };
+
+ array.forEach((char, i) => {
+ if (char === array[i - 1]) {
+ prev_char_count++;
+ return;
+ }
+
+ prev_pattern();
+ pattern += char;
+ prev_char_count = 1;
+ });
+ prev_pattern();
+ return pattern;
+};
+/**
+ * Convert array of strings to a regular expression
+ * ex ['ab','a'] => (?:ab|a)
+ * ex ['a','b'] => [ab]
+ * @param {Set} chars
+ * @return {string}
+ */
+
+const setToPattern = chars => {
+ let array = toArray(chars);
+ return arrayToPattern(array);
+};
+/**
+ *
+ * https://stackoverflow.com/questions/7376598/in-javascript-how-do-i-check-if-an-array-has-duplicate-values
+ * @param {any[]} array
+ */
+
+const hasDuplicates = array => {
+ return new Set(array).size !== array.length;
+};
+/**
+ * https://stackoverflow.com/questions/63006601/why-does-u-throw-an-invalid-escape-error
+ * @param {string} str
+ * @return {string}
+ */
+
+const escape_regex = str => {
+ return (str + '').replace(/([\$\(\)\*\+\.\?\[\]\^\{\|\}\\])/gu, '\\$1');
+};
+/**
+ * Return the max length of array values
+ * @param {string[]} array
+ *
+ */
+
+const maxValueLength = array => {
+ return array.reduce((longest, value) => Math.max(longest, unicodeLength(value)), 0);
+};
+/**
+ * @param {string} str
+ */
+
+const unicodeLength = str => {
+ return toArray(str).length;
+};
+/**
+ * @param {any} p
+ * @return {any[]}
+ */
+
+const toArray = p => Array.from(p);
+
+/*! @orchidjs/unicode-variants | https://github.com/orchidjs/unicode-variants | Apache License (v2) */
+/**
+ * Get all possible combinations of substrings that add up to the given string
+ * https://stackoverflow.com/questions/30169587/find-all-the-combination-of-substrings-that-add-up-to-the-given-string
+ * @param {string} input
+ * @return {string[][]}
+ */
+const allSubstrings = input => {
+ if (input.length === 1) return [[input]];
+ /** @type {string[][]} */
+
+ let result = [];
+ const start = input.substring(1);
+ const suba = allSubstrings(start);
+ suba.forEach(function (subresult) {
+ let tmp = subresult.slice(0);
+ tmp[0] = input.charAt(0) + tmp[0];
+ result.push(tmp);
+ tmp = subresult.slice(0);
+ tmp.unshift(input.charAt(0));
+ result.push(tmp);
+ });
+ return result;
+};
+
+/*! @orchidjs/unicode-variants | https://github.com/orchidjs/unicode-variants | Apache License (v2) */
+
+/**
+ * @typedef {{[key:string]:string}} TUnicodeMap
+ * @typedef {{[key:string]:Set}} TUnicodeSets
+ * @typedef {[[number,number]]} TCodePoints
+ * @typedef {{folded:string,composed:string,code_point:number}} TCodePointObj
+ * @typedef {{start:number,end:number,length:number,substr:string}} TSequencePart
+ */
+/** @type {TCodePoints} */
+
+const code_points = [[0, 65535]];
+const accent_pat = '[\u0300-\u036F\u{b7}\u{2be}\u{2bc}]';
+/** @type {TUnicodeMap} */
+
+let unicode_map;
+/** @type {RegExp} */
+
+let multi_char_reg;
+const max_char_length = 3;
+/** @type {TUnicodeMap} */
+
+const latin_convert = {};
+/** @type {TUnicodeMap} */
+
+const latin_condensed = {
+ '/': '⁄∕',
+ '0': '߀',
+ "a": "ⱥɐɑ",
+ "aa": "ꜳ",
+ "ae": "æǽǣ",
+ "ao": "ꜵ",
+ "au": "ꜷ",
+ "av": "ꜹꜻ",
+ "ay": "ꜽ",
+ "b": "ƀɓƃ",
+ "c": "ꜿƈȼↄ",
+ "d": "đɗɖᴅƌꮷԁɦ",
+ "e": "ɛǝᴇɇ",
+ "f": "ꝼƒ",
+ "g": "ǥɠꞡᵹꝿɢ",
+ "h": "ħⱨⱶɥ",
+ "i": "ɨı",
+ "j": "ɉȷ",
+ "k": "ƙⱪꝁꝃꝅꞣ",
+ "l": "łƚɫⱡꝉꝇꞁɭ",
+ "m": "ɱɯϻ",
+ "n": "ꞥƞɲꞑᴎлԉ",
+ "o": "øǿɔɵꝋꝍᴑ",
+ "oe": "œ",
+ "oi": "ƣ",
+ "oo": "ꝏ",
+ "ou": "ȣ",
+ "p": "ƥᵽꝑꝓꝕρ",
+ "q": "ꝗꝙɋ",
+ "r": "ɍɽꝛꞧꞃ",
+ "s": "ßȿꞩꞅʂ",
+ "t": "ŧƭʈⱦꞇ",
+ "th": "þ",
+ "tz": "ꜩ",
+ "u": "ʉ",
+ "v": "ʋꝟʌ",
+ "vy": "ꝡ",
+ "w": "ⱳ",
+ "y": "ƴɏỿ",
+ "z": "ƶȥɀⱬꝣ",
+ "hv": "ƕ"
+};
+
+for (let latin in latin_condensed) {
+ let unicode = latin_condensed[latin] || '';
+
+ for (let i = 0; i < unicode.length; i++) {
+ let char = unicode.substring(i, i + 1);
+ latin_convert[char] = latin;
+ }
+}
+
+const convert_pat = new RegExp(Object.keys(latin_convert).join('|') + '|' + accent_pat, 'gu');
+/**
+ * Initialize the unicode_map from the give code point ranges
+ *
+ * @param {TCodePoints=} _code_points
+ */
+
+const initialize = _code_points => {
+ if (unicode_map !== undefined) return;
+ unicode_map = generateMap(_code_points || code_points);
+};
+/**
+ * Helper method for normalize a string
+ * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/normalize
+ * @param {string} str
+ * @param {string} form
+ */
+
+const normalize = (str, form = 'NFKD') => str.normalize(form);
+/**
+ * Remove accents without reordering string
+ * calling str.normalize('NFKD') on \u{594}\u{595}\u{596} becomes \u{596}\u{594}\u{595}
+ * via https://github.com/krisk/Fuse/issues/133#issuecomment-318692703
+ * @param {string} str
+ * @return {string}
+ */
+
+const asciifold = str => {
+ return toArray(str).reduce(
+ /**
+ * @param {string} result
+ * @param {string} char
+ */
+ (result, char) => {
+ return result + _asciifold(char);
+ }, '');
+};
+/**
+ * @param {string} str
+ * @return {string}
+ */
+
+const _asciifold = str => {
+ str = normalize(str).toLowerCase().replace(convert_pat, (
+ /** @type {string} */
+ char) => {
+ return latin_convert[char] || '';
+ }); //return str;
+
+ return normalize(str, 'NFC');
+};
+/**
+ * Generate a list of unicode variants from the list of code points
+ * @param {TCodePoints} code_points
+ * @yield {TCodePointObj}
+ */
+
+function* generator(code_points) {
+ for (const [code_point_min, code_point_max] of code_points) {
+ for (let i = code_point_min; i <= code_point_max; i++) {
+ let composed = String.fromCharCode(i);
+ let folded = asciifold(composed);
+
+ if (folded == composed.toLowerCase()) {
+ continue;
+ } // skip when folded is a string longer than 3 characters long
+ // bc the resulting regex patterns will be long
+ // eg:
+ // folded صلى الله عليه وسلم length 18 code point 65018
+ // folded جل جلاله length 8 code point 65019
+
+
+ if (folded.length > max_char_length) {
+ continue;
+ }
+
+ if (folded.length == 0) {
+ continue;
+ }
+
+ yield {
+ folded: folded,
+ composed: composed,
+ code_point: i
+ };
+ }
+ }
+}
+/**
+ * Generate a unicode map from the list of code points
+ * @param {TCodePoints} code_points
+ * @return {TUnicodeSets}
+ */
+
+const generateSets = code_points => {
+ /** @type {{[key:string]:Set}} */
+ const unicode_sets = {};
+ /**
+ * @param {string} folded
+ * @param {string} to_add
+ */
+
+ const addMatching = (folded, to_add) => {
+ /** @type {Set} */
+ const folded_set = unicode_sets[folded] || new Set();
+ const patt = new RegExp('^' + setToPattern(folded_set) + '$', 'iu');
+
+ if (to_add.match(patt)) {
+ return;
+ }
+
+ folded_set.add(escape_regex(to_add));
+ unicode_sets[folded] = folded_set;
+ };
+
+ for (let value of generator(code_points)) {
+ addMatching(value.folded, value.folded);
+ addMatching(value.folded, value.composed);
+ }
+
+ return unicode_sets;
+};
+/**
+ * Generate a unicode map from the list of code points
+ * ae => (?:(?:ae|Æ|Ǽ|Ǣ)|(?:A|Ⓐ|A...)(?:E|ɛ|Ⓔ...))
+ *
+ * @param {TCodePoints} code_points
+ * @return {TUnicodeMap}
+ */
+
+const generateMap = code_points => {
+ /** @type {TUnicodeSets} */
+ const unicode_sets = generateSets(code_points);
+ /** @type {TUnicodeMap} */
+
+ const unicode_map = {};
+ /** @type {string[]} */
+
+ let multi_char = [];
+
+ for (let folded in unicode_sets) {
+ let set = unicode_sets[folded];
+
+ if (set) {
+ unicode_map[folded] = setToPattern(set);
+ }
+
+ if (folded.length > 1) {
+ multi_char.push(escape_regex(folded));
+ }
+ }
+
+ multi_char.sort((a, b) => b.length - a.length);
+ const multi_char_patt = arrayToPattern(multi_char);
+ multi_char_reg = new RegExp('^' + multi_char_patt, 'u');
+ return unicode_map;
+};
+/**
+ * Map each element of an array from it's folded value to all possible unicode matches
+ * @param {string[]} strings
+ * @param {number} min_replacement
+ * @return {string}
+ */
+
+const mapSequence = (strings, min_replacement = 1) => {
+ let chars_replaced = 0;
+ strings = strings.map(str => {
+ if (unicode_map[str]) {
+ chars_replaced += str.length;
+ }
+
+ return unicode_map[str] || str;
+ });
+
+ if (chars_replaced >= min_replacement) {
+ return sequencePattern(strings);
+ }
+
+ return '';
+};
+/**
+ * Convert a short string and split it into all possible patterns
+ * Keep a pattern only if min_replacement is met
+ *
+ * 'abc'
+ * => [['abc'],['ab','c'],['a','bc'],['a','b','c']]
+ * => ['abc-pattern','ab-c-pattern'...]
+ *
+ *
+ * @param {string} str
+ * @param {number} min_replacement
+ * @return {string}
+ */
+
+const substringsToPattern = (str, min_replacement = 1) => {
+ min_replacement = Math.max(min_replacement, str.length - 1);
+ return arrayToPattern(allSubstrings(str).map(sub_pat => {
+ return mapSequence(sub_pat, min_replacement);
+ }));
+};
+/**
+ * Convert an array of sequences into a pattern
+ * [{start:0,end:3,length:3,substr:'iii'}...] => (?:iii...)
+ *
+ * @param {Sequence[]} sequences
+ * @param {boolean} all
+ */
+
+const sequencesToPattern = (sequences, all = true) => {
+ let min_replacement = sequences.length > 1 ? 1 : 0;
+ return arrayToPattern(sequences.map(sequence => {
+ let seq = [];
+ const len = all ? sequence.length() : sequence.length() - 1;
+
+ for (let j = 0; j < len; j++) {
+ seq.push(substringsToPattern(sequence.substrs[j] || '', min_replacement));
+ }
+
+ return sequencePattern(seq);
+ }));
+};
+/**
+ * Return true if the sequence is already in the sequences
+ * @param {Sequence} needle_seq
+ * @param {Sequence[]} sequences
+ */
+
+
+const inSequences = (needle_seq, sequences) => {
+ for (const seq of sequences) {
+ if (seq.start != needle_seq.start || seq.end != needle_seq.end) {
+ continue;
+ }
+
+ if (seq.substrs.join('') !== needle_seq.substrs.join('')) {
+ continue;
+ }
+
+ let needle_parts = needle_seq.parts;
+ /**
+ * @param {TSequencePart} part
+ */
+
+ const filter = part => {
+ for (const needle_part of needle_parts) {
+ if (needle_part.start === part.start && needle_part.substr === part.substr) {
+ return false;
+ }
+
+ if (part.length == 1 || needle_part.length == 1) {
+ continue;
+ } // check for overlapping parts
+ // a = ['::=','==']
+ // b = ['::','===']
+ // a = ['r','sm']
+ // b = ['rs','m']
+
+
+ if (part.start < needle_part.start && part.end > needle_part.start) {
+ return true;
+ }
+
+ if (needle_part.start < part.start && needle_part.end > part.start) {
+ return true;
+ }
+ }
+
+ return false;
+ };
+
+ let filtered = seq.parts.filter(filter);
+
+ if (filtered.length > 0) {
+ continue;
+ }
+
+ return true;
+ }
+
+ return false;
+};
+
+class Sequence {
+ constructor() {
+ /** @type {TSequencePart[]} */
+ this.parts = [];
+ /** @type {string[]} */
+
+ this.substrs = [];
+ this.start = 0;
+ this.end = 0;
+ }
+ /**
+ * @param {TSequencePart|undefined} part
+ */
+
+
+ add(part) {
+ if (part) {
+ this.parts.push(part);
+ this.substrs.push(part.substr);
+ this.start = Math.min(part.start, this.start);
+ this.end = Math.max(part.end, this.end);
+ }
+ }
+
+ last() {
+ return this.parts[this.parts.length - 1];
+ }
+
+ length() {
+ return this.parts.length;
+ }
+ /**
+ * @param {number} position
+ * @param {TSequencePart} last_piece
+ */
+
+
+ clone(position, last_piece) {
+ let clone = new Sequence();
+ let parts = JSON.parse(JSON.stringify(this.parts));
+ let last_part = parts.pop();
+
+ for (const part of parts) {
+ clone.add(part);
+ }
+
+ let last_substr = last_piece.substr.substring(0, position - last_part.start);
+ let clone_last_len = last_substr.length;
+ clone.add({
+ start: last_part.start,
+ end: last_part.start + clone_last_len,
+ length: clone_last_len,
+ substr: last_substr
+ });
+ return clone;
+ }
+
+}
+/**
+ * Expand a regular expression pattern to include unicode variants
+ * eg /a/ becomes /aⓐaẚàáâầấẫẩãāăằắẵẳȧǡäǟảåǻǎȁȃạậặḁąⱥɐɑAⒶAÀÁÂẦẤẪẨÃĀĂẰẮẴẲȦǠÄǞẢÅǺǍȀȂẠẬẶḀĄȺⱯ/
+ *
+ * Issue:
+ * ﺊﺋ [ 'ﺊ = \\u{fe8a}', 'ﺋ = \\u{fe8b}' ]
+ * becomes: ئئ [ 'ي = \\u{64a}', 'ٔ = \\u{654}', 'ي = \\u{64a}', 'ٔ = \\u{654}' ]
+ *
+ * İIJ = IIJ = ⅡJ
+ *
+ * 1/2/4
+ *
+ * @param {string} str
+ * @return {string|undefined}
+ */
+
+
+const getPattern = str => {
+ initialize();
+ str = asciifold(str);
+ let pattern = '';
+ let sequences = [new Sequence()];
+
+ for (let i = 0; i < str.length; i++) {
+ let substr = str.substring(i);
+ let match = substr.match(multi_char_reg);
+ const char = str.substring(i, i + 1);
+ const match_str = match ? match[0] : null; // loop through sequences
+ // add either the char or multi_match
+
+ let overlapping = [];
+ let added_types = new Set();
+
+ for (const sequence of sequences) {
+ const last_piece = sequence.last();
+
+ if (!last_piece || last_piece.length == 1 || last_piece.end <= i) {
+ // if we have a multi match
+ if (match_str) {
+ const len = match_str.length;
+ sequence.add({
+ start: i,
+ end: i + len,
+ length: len,
+ substr: match_str
+ });
+ added_types.add('1');
+ } else {
+ sequence.add({
+ start: i,
+ end: i + 1,
+ length: 1,
+ substr: char
+ });
+ added_types.add('2');
+ }
+ } else if (match_str) {
+ let clone = sequence.clone(i, last_piece);
+ const len = match_str.length;
+ clone.add({
+ start: i,
+ end: i + len,
+ length: len,
+ substr: match_str
+ });
+ overlapping.push(clone);
+ } else {
+ // don't add char
+ // adding would create invalid patterns: 234 => [2,34,4]
+ added_types.add('3');
+ }
+ } // if we have overlapping
+
+
+ if (overlapping.length > 0) {
+ // ['ii','iii'] before ['i','i','iii']
+ overlapping = overlapping.sort((a, b) => {
+ return a.length() - b.length();
+ });
+
+ for (let clone of overlapping) {
+ // don't add if we already have an equivalent sequence
+ if (inSequences(clone, sequences)) {
+ continue;
+ }
+
+ sequences.push(clone);
+ }
+
+ continue;
+ } // if we haven't done anything unique
+ // clean up the patterns
+ // helps keep patterns smaller
+ // if str = 'r₨㎧aarss', pattern will be 446 instead of 655
+
+
+ if (i > 0 && added_types.size == 1 && !added_types.has('3')) {
+ pattern += sequencesToPattern(sequences, false);
+ let new_seq = new Sequence();
+ const old_seq = sequences[0];
+
+ if (old_seq) {
+ new_seq.add(old_seq.last());
+ }
+
+ sequences = [new_seq];
+ }
+ }
+
+ pattern += sequencesToPattern(sequences, true);
+ return pattern;
+};
+
+/*! sifter.js | https://github.com/orchidjs/sifter.js | Apache License (v2) */
+
+/**
+ * A property getter resolving dot-notation
+ * @param {Object} obj The root object to fetch property on
+ * @param {String} name The optionally dotted property name to fetch
+ * @return {Object} The resolved property value
+ */
+const getAttr = (obj, name) => {
+ if (!obj) return;
+ return obj[name];
+};
+/**
+ * A property getter resolving dot-notation
+ * @param {Object} obj The root object to fetch property on
+ * @param {String} name The optionally dotted property name to fetch
+ * @return {Object} The resolved property value
+ */
+
+const getAttrNesting = (obj, name) => {
+ if (!obj) return;
+ var part,
+ names = name.split(".");
+
+ while ((part = names.shift()) && (obj = obj[part]));
+
+ return obj;
+};
+/**
+ * Calculates how close of a match the
+ * given value is against a search token.
+ *
+ */
+
+const scoreValue = (value, token, weight) => {
+ var score, pos;
+ if (!value) return 0;
+ value = value + '';
+ if (token.regex == null) return 0;
+ pos = value.search(token.regex);
+ if (pos === -1) return 0;
+ score = token.string.length / value.length;
+ if (pos === 0) score += 0.5;
+ return score * weight;
+};
+/**
+ * Cast object property to an array if it exists and has a value
+ *
+ */
+
+const propToArray = (obj, key) => {
+ var value = obj[key];
+ if (typeof value == 'function') return value;
+
+ if (value && !Array.isArray(value)) {
+ obj[key] = [value];
+ }
+};
+/**
+ * Iterates over arrays and hashes.
+ *
+ * ```
+ * iterate(this.items, function(item, id) {
+ * // invoked for each item
+ * });
+ * ```
+ *
+ */
+
+const iterate$1 = (object, callback) => {
+ if (Array.isArray(object)) {
+ object.forEach(callback);
+ } else {
+ for (var key in object) {
+ if (object.hasOwnProperty(key)) {
+ callback(object[key], key);
+ }
+ }
+ }
+};
+const cmp = (a, b) => {
+ if (typeof a === 'number' && typeof b === 'number') {
+ return a > b ? 1 : a < b ? -1 : 0;
+ }
+
+ a = asciifold(a + '').toLowerCase();
+ b = asciifold(b + '').toLowerCase();
+ if (a > b) return 1;
+ if (b > a) return -1;
+ return 0;
+};
+
+/*! sifter.js | https://github.com/orchidjs/sifter.js | Apache License (v2) */
+
+/**
+ * sifter.js
+ * Copyright (c) 2013–2020 Brian Reavis & contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this
+ * file except in compliance with the License. You may obtain a copy of the License at:
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
+ * ANY KIND, either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ *
+ * @author Brian Reavis
+ */
+
+class Sifter {
+ // []|{};
+
+ /**
+ * Textually searches arrays and hashes of objects
+ * by property (or multiple properties). Designed
+ * specifically for autocomplete.
+ *
+ */
+ constructor(items, settings) {
+ this.items = void 0;
+ this.settings = void 0;
+ this.items = items;
+ this.settings = settings || {
+ diacritics: true
+ };
+ }
+
+ /**
+ * Splits a search string into an array of individual
+ * regexps to be used to match results.
+ *
+ */
+ tokenize(query, respect_word_boundaries, weights) {
+ if (!query || !query.length) return [];
+ const tokens = [];
+ const words = query.split(/\s+/);
+ var field_regex;
+
+ if (weights) {
+ field_regex = new RegExp('^(' + Object.keys(weights).map(escape_regex).join('|') + ')\:(.*)$');
+ }
+
+ words.forEach(word => {
+ let field_match;
+ let field = null;
+ let regex = null; // look for "field:query" tokens
+
+ if (field_regex && (field_match = word.match(field_regex))) {
+ field = field_match[1];
+ word = field_match[2];
+ }
+
+ if (word.length > 0) {
+ if (this.settings.diacritics) {
+ regex = getPattern(word) || null;
+ } else {
+ regex = escape_regex(word);
+ }
+
+ if (regex && respect_word_boundaries) regex = "\\b" + regex;
+ }
+
+ tokens.push({
+ string: word,
+ regex: regex ? new RegExp(regex, 'iu') : null,
+ field: field
+ });
+ });
+ return tokens;
+ }
+
+ /**
+ * Returns a function to be used to score individual results.
+ *
+ * Good matches will have a higher score than poor matches.
+ * If an item is not a match, 0 will be returned by the function.
+ *
+ * @returns {T.ScoreFn}
+ */
+ getScoreFunction(query, options) {
+ var search = this.prepareSearch(query, options);
+ return this._getScoreFunction(search);
+ }
+ /**
+ * @returns {T.ScoreFn}
+ *
+ */
+
+
+ _getScoreFunction(search) {
+ const tokens = search.tokens,
+ token_count = tokens.length;
+
+ if (!token_count) {
+ return function () {
+ return 0;
+ };
+ }
+
+ const fields = search.options.fields,
+ weights = search.weights,
+ field_count = fields.length,
+ getAttrFn = search.getAttrFn;
+
+ if (!field_count) {
+ return function () {
+ return 1;
+ };
+ }
+ /**
+ * Calculates the score of an object
+ * against the search query.
+ *
+ */
+
+
+ const scoreObject = function () {
+ if (field_count === 1) {
+ return function (token, data) {
+ const field = fields[0].field;
+ return scoreValue(getAttrFn(data, field), token, weights[field] || 1);
+ };
+ }
+
+ return function (token, data) {
+ var sum = 0; // is the token specific to a field?
+
+ if (token.field) {
+ const value = getAttrFn(data, token.field);
+
+ if (!token.regex && value) {
+ sum += 1 / field_count;
+ } else {
+ sum += scoreValue(value, token, 1);
+ }
+ } else {
+ iterate$1(weights, (weight, field) => {
+ sum += scoreValue(getAttrFn(data, field), token, weight);
+ });
+ }
+
+ return sum / field_count;
+ };
+ }();
+
+ if (token_count === 1) {
+ return function (data) {
+ return scoreObject(tokens[0], data);
+ };
+ }
+
+ if (search.options.conjunction === 'and') {
+ return function (data) {
+ var score,
+ sum = 0;
+
+ for (let token of tokens) {
+ score = scoreObject(token, data);
+ if (score <= 0) return 0;
+ sum += score;
+ }
+
+ return sum / token_count;
+ };
+ } else {
+ return function (data) {
+ var sum = 0;
+ iterate$1(tokens, token => {
+ sum += scoreObject(token, data);
+ });
+ return sum / token_count;
+ };
+ }
+ }
+
+ /**
+ * Returns a function that can be used to compare two
+ * results, for sorting purposes. If no sorting should
+ * be performed, `null` will be returned.
+ *
+ * @return function(a,b)
+ */
+ getSortFunction(query, options) {
+ var search = this.prepareSearch(query, options);
+ return this._getSortFunction(search);
+ }
+
+ _getSortFunction(search) {
+ var implicit_score,
+ sort_flds = [];
+ const self = this,
+ options = search.options,
+ sort = !search.query && options.sort_empty ? options.sort_empty : options.sort;
+
+ if (typeof sort == 'function') {
+ return sort.bind(this);
+ }
+ /**
+ * Fetches the specified sort field value
+ * from a search result item.
+ *
+ */
+
+
+ const get_field = function get_field(name, result) {
+ if (name === '$score') return result.score;
+ return search.getAttrFn(self.items[result.id], name);
+ }; // parse options
+
+
+ if (sort) {
+ for (let s of sort) {
+ if (search.query || s.field !== '$score') {
+ sort_flds.push(s);
+ }
+ }
+ } // the "$score" field is implied to be the primary
+ // sort field, unless it's manually specified
+
+
+ if (search.query) {
+ implicit_score = true;
+
+ for (let fld of sort_flds) {
+ if (fld.field === '$score') {
+ implicit_score = false;
+ break;
+ }
+ }
+
+ if (implicit_score) {
+ sort_flds.unshift({
+ field: '$score',
+ direction: 'desc'
+ });
+ } // without a search.query, all items will have the same score
+
+ } else {
+ sort_flds = sort_flds.filter(fld => fld.field !== '$score');
+ } // build function
+
+
+ const sort_flds_count = sort_flds.length;
+
+ if (!sort_flds_count) {
+ return null;
+ }
+
+ return function (a, b) {
+ var result, field;
+
+ for (let sort_fld of sort_flds) {
+ field = sort_fld.field;
+ let multiplier = sort_fld.direction === 'desc' ? -1 : 1;
+ result = multiplier * cmp(get_field(field, a), get_field(field, b));
+ if (result) return result;
+ }
+
+ return 0;
+ };
+ }
+
+ /**
+ * Parses a search query and returns an object
+ * with tokens and fields ready to be populated
+ * with results.
+ *
+ */
+ prepareSearch(query, optsUser) {
+ const weights = {};
+ var options = Object.assign({}, optsUser);
+ propToArray(options, 'sort');
+ propToArray(options, 'sort_empty'); // convert fields to new format
+
+ if (options.fields) {
+ propToArray(options, 'fields');
+ const fields = [];
+ options.fields.forEach(field => {
+ if (typeof field == 'string') {
+ field = {
+ field: field,
+ weight: 1
+ };
+ }
+
+ fields.push(field);
+ weights[field.field] = 'weight' in field ? field.weight : 1;
+ });
+ options.fields = fields;
+ }
+
+ return {
+ options: options,
+ query: query.toLowerCase().trim(),
+ tokens: this.tokenize(query, options.respect_word_boundaries, weights),
+ total: 0,
+ items: [],
+ weights: weights,
+ getAttrFn: options.nesting ? getAttrNesting : getAttr
+ };
+ }
+
+ /**
+ * Searches through all items and returns a sorted array of matches.
+ *
+ */
+ search(query, options) {
+ var self = this,
+ score,
+ search;
+ search = this.prepareSearch(query, options);
+ options = search.options;
+ query = search.query; // generate result scoring function
+
+ const fn_score = options.score || self._getScoreFunction(search); // perform search and sort
+
+
+ if (query.length) {
+ iterate$1(self.items, (item, id) => {
+ score = fn_score(item);
+
+ if (options.filter === false || score > 0) {
+ search.items.push({
+ 'score': score,
+ 'id': id
+ });
+ }
+ });
+ } else {
+ iterate$1(self.items, (_, id) => {
+ search.items.push({
+ 'score': 1,
+ 'id': id
+ });
+ });
+ }
+
+ const fn_sort = self._getSortFunction(search);
+
+ if (fn_sort) search.items.sort(fn_sort); // apply limits
+
+ search.total = search.items.length;
+
+ if (typeof options.limit === 'number') {
+ search.items = search.items.slice(0, options.limit);
+ }
+
+ return search;
+ }
+
+}
+
+/**
+ * Iterates over arrays and hashes.
+ *
+ * ```
+ * iterate(this.items, function(item, id) {
+ * // invoked for each item
+ * });
+ * ```
+ *
+ */
+const iterate = (object, callback) => {
+ if (Array.isArray(object)) {
+ object.forEach(callback);
+ } else {
+ for (var key in object) {
+ if (object.hasOwnProperty(key)) {
+ callback(object[key], key);
+ }
+ }
+ }
+};
+
+/**
+ * Return a dom element from either a dom query string, jQuery object, a dom element or html string
+ * https://stackoverflow.com/questions/494143/creating-a-new-dom-element-from-an-html-string-using-built-in-dom-methods-or-pro/35385518#35385518
+ *
+ * param query should be {}
+ */
+const getDom = query => {
+ if (query.jquery) {
+ return query[0];
+ }
+ if (query instanceof HTMLElement) {
+ return query;
+ }
+ if (isHtmlString(query)) {
+ var tpl = document.createElement('template');
+ tpl.innerHTML = query.trim(); // Never return a text node of whitespace as the result
+ return tpl.content.firstChild;
+ }
+ return document.querySelector(query);
+};
+const isHtmlString = arg => {
+ if (typeof arg === 'string' && arg.indexOf('<') > -1) {
+ return true;
+ }
+ return false;
+};
+const escapeQuery = query => {
+ return query.replace(/['"\\]/g, '\\$&');
+};
+
+/**
+ * Dispatch an event
+ *
+ */
+const triggerEvent = (dom_el, event_name) => {
+ var event = document.createEvent('HTMLEvents');
+ event.initEvent(event_name, true, false);
+ dom_el.dispatchEvent(event);
+};
+
+/**
+ * Apply CSS rules to a dom element
+ *
+ */
+const applyCSS = (dom_el, css) => {
+ Object.assign(dom_el.style, css);
+};
+
+/**
+ * Add css classes
+ *
+ */
+const addClasses = (elmts, ...classes) => {
+ var norm_classes = classesArray(classes);
+ elmts = castAsArray(elmts);
+ elmts.map(el => {
+ norm_classes.map(cls => {
+ el.classList.add(cls);
+ });
+ });
+};
+
+/**
+ * Remove css classes
+ *
+ */
+const removeClasses = (elmts, ...classes) => {
+ var norm_classes = classesArray(classes);
+ elmts = castAsArray(elmts);
+ elmts.map(el => {
+ norm_classes.map(cls => {
+ el.classList.remove(cls);
+ });
+ });
+};
+
+/**
+ * Return arguments
+ *
+ */
+const classesArray = args => {
+ var classes = [];
+ iterate(args, _classes => {
+ if (typeof _classes === 'string') {
+ _classes = _classes.trim().split(/[\11\12\14\15\40]/);
+ }
+ if (Array.isArray(_classes)) {
+ classes = classes.concat(_classes);
+ }
+ });
+ return classes.filter(Boolean);
+};
+
+/**
+ * Create an array from arg if it's not already an array
+ *
+ */
+const castAsArray = arg => {
+ if (!Array.isArray(arg)) {
+ arg = [arg];
+ }
+ return arg;
+};
+
+/**
+ * Get the closest node to the evt.target matching the selector
+ * Stops at wrapper
+ *
+ */
+const parentMatch = (target, selector, wrapper) => {
+ if (wrapper && !wrapper.contains(target)) {
+ return;
+ }
+ while (target && target.matches) {
+ if (target.matches(selector)) {
+ return target;
+ }
+ target = target.parentNode;
+ }
+};
+
+/**
+ * Get the first or last item from an array
+ *
+ * > 0 - right (last)
+ * <= 0 - left (first)
+ *
+ */
+const getTail = (list, direction = 0) => {
+ if (direction > 0) {
+ return list[list.length - 1];
+ }
+ return list[0];
+};
+
+/**
+ * Return true if an object is empty
+ *
+ */
+const isEmptyObject = obj => {
+ return Object.keys(obj).length === 0;
+};
+
+/**
+ * Get the index of an element amongst sibling nodes of the same type
+ *
+ */
+const nodeIndex = (el, amongst) => {
+ if (!el) return -1;
+ amongst = amongst || el.nodeName;
+ var i = 0;
+ while (el = el.previousElementSibling) {
+ if (el.matches(amongst)) {
+ i++;
+ }
+ }
+ return i;
+};
+
+/**
+ * Set attributes of an element
+ *
+ */
+const setAttr = (el, attrs) => {
+ iterate(attrs, (val, attr) => {
+ if (val == null) {
+ el.removeAttribute(attr);
+ } else {
+ el.setAttribute(attr, '' + val);
+ }
+ });
+};
+
+/**
+ * Replace a node
+ */
+const replaceNode = (existing, replacement) => {
+ if (existing.parentNode) existing.parentNode.replaceChild(replacement, existing);
+};
+
+/**
+ * highlight v3 | MIT license | Johann Burkard
+ * Highlights arbitrary terms in a node.
+ *
+ * - Modified by Marshal 2011-6-24 (added regex)
+ * - Modified by Brian Reavis 2012-8-27 (cleanup)
+ */
+
+const highlight = (element, regex) => {
+ if (regex === null) return;
+
+ // convet string to regex
+ if (typeof regex === 'string') {
+ if (!regex.length) return;
+ regex = new RegExp(regex, 'i');
+ }
+
+ // Wrap matching part of text node with highlighting , e.g.
+ // Soccer -> Soccer for regex = /soc/i
+ const highlightText = node => {
+ var match = node.data.match(regex);
+ if (match && node.data.length > 0) {
+ var spannode = document.createElement('span');
+ spannode.className = 'highlight';
+ var middlebit = node.splitText(match.index);
+ middlebit.splitText(match[0].length);
+ var middleclone = middlebit.cloneNode(true);
+ spannode.appendChild(middleclone);
+ replaceNode(middlebit, spannode);
+ return 1;
+ }
+ return 0;
+ };
+
+ // Recurse element node, looking for child text nodes to highlight, unless element
+ // is childless,