diff --git a/e107_web/lib/bootstrap-suggest/LICENSE b/e107_web/lib/bootstrap-suggest/LICENSE
new file mode 100644
index 000000000..b49373e51
--- /dev/null
+++ b/e107_web/lib/bootstrap-suggest/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2019 Jovanni Lo
+
+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/e107_web/lib/bootstrap-suggest/README.md b/e107_web/lib/bootstrap-suggest/README.md
new file mode 100644
index 000000000..7f7e28015
--- /dev/null
+++ b/e107_web/lib/bootstrap-suggest/README.md
@@ -0,0 +1,69 @@
+bootstrap-suggest
+============================
+A bootstrap plugin for your mention needs.
+
+
+
+## V2
+The version 2 of this plugin now supports bootstrap 4 and `contenteditable` that uses `jquery.caret` (optional).
+
+## Install
+Several quick start options are available:
+
+- [download](https://github.com/lodev09/bootstrap-suggest/archive/v2.0.2.zip) latest release
+- [npm](https://www.npmjs.com/package/bootstrap-suggest): `npm install --save bootstrap-suggest`
+- [bower](https://bower.io): `bower install bootstrap-suggest`
+
+** Make sure to link `bootstrap-suggest.js` and `bootstrap-suggest.css` to your project
+
+## Usage
+
+### Markup
+```html
+
+ start typing with @
+
+
+```
+
+### Data
+```
+var users = [
+ {username: 'lodev09', fullname: 'Jovanni Lo'},
+ {username: 'foo', fullname: 'Foo User'},
+ {username: 'bar', fullname: 'Bar User'},
+ {username: 'twbs', fullname: 'Twitter Bootstrap'},
+ {username: 'john', fullname: 'John Doe'},
+ {username: 'jane', fullname: 'Jane Doe'},
+];
+```
+
+### Init
+```javascript
+$('#comment').suggest('@', {
+ data: users,
+ map: function(user) {
+ return {
+ value: user.username,
+ text: ''+user.username+' '+user.fullname+' '
+ }
+ }
+})
+```
+
+## API
+http://lodev09.github.io/bootstrap-suggest/#api
+
+## Feedback
+All bugs, feature requests, pull requests, feedback, etc., are welcome. Visit my site at [www.lodev09.com](http://www.lodev09.com "www.lodev09.com").
+
+[](mailto:lodev09@gmail.com)
+
+## Credits
+© 2018 - Coded by Jovanni Lo / [@lodev09](http://twitter.com/lodev09)
+
+## License
+
+Released under the MIT license. See [LICENSE](LICENSE) file.
+
+[](http://opensource.org/licenses/MIT)
diff --git a/e107_web/lib/bootstrap-suggest/dist/bootstrap-suggest.css b/e107_web/lib/bootstrap-suggest/dist/bootstrap-suggest.css
new file mode 100644
index 000000000..09e9c3e69
--- /dev/null
+++ b/e107_web/lib/bootstrap-suggest/dist/bootstrap-suggest.css
@@ -0,0 +1,29 @@
+/*!
+ * bootstra-suggest - v2.0.3 (https://github.com/lodev09/bootstrap-suggest#readme)
+ * Copyright 2013-2019 Jovanni Lo (lodev09@gmail.com)
+ * Licensed under MIT (https://github.com/lodev09/bootstrap-suggest/blob/master/LICENSE)
+ */
+
+.suggest {
+ /* position: relative; */
+ top: 7px;
+ z-index: 30;
+ text-align: left;
+}
+
+.suggest > .dropdown-menu {
+ margin-top: 15px;
+ position: absolute;
+ height: 200px;
+ overflow-y: scroll
+}
+
+.suggest > .dropdown-menu > a.dropdown-item {
+ border-top: 1px solid #eeeeee;
+ padding: 5px 10px;
+}
+
+.suggest > .dropdown-menu > a.dropdown-item:first-child {
+ border-top: 0;
+}
+
diff --git a/e107_web/lib/bootstrap-suggest/dist/bootstrap-suggest.js b/e107_web/lib/bootstrap-suggest/dist/bootstrap-suggest.js
new file mode 100644
index 000000000..fb277aa4c
--- /dev/null
+++ b/e107_web/lib/bootstrap-suggest/dist/bootstrap-suggest.js
@@ -0,0 +1,592 @@
+/* ===================================================
+* bootstrap-suggest.js v2.0.3
+* http://github.com/lodev09/bootstrap-suggest
+* ===================================================
+* Copyright 2019 Jovanni Lo @lodev09
+*
+* 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://opensource.org/licenses/MIT
+*
+* 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.
+* ========================================================== */
+
+(function ($) {
+
+ "use strict"; // jshint ;_;
+
+ var Suggest = function(el, key, options) {
+ var that = this;
+
+ this.$element = $(el);
+ this.$items = undefined;
+ this.options = $.extend(true, {}, $.fn.suggest.defaults, options, this.$element.data(), this.$element.data('options'));
+ this.key = key;
+ this.isShown = false;
+ this.query = '';
+ this._queryPos = [];
+ this._keyPos = -1;
+
+ this.$dropdown = $('
', {
+ 'class': 'dropdown suggest ' + this.options.dropdownClass,
+ 'html': $('', {'class': 'dropdown-menu', role: 'menu'}),
+ 'data-key': this.key
+ });
+
+ this.load();
+
+ };
+
+ Suggest.prototype = {
+ __setListener: function() {
+ this.$element
+ .on('suggest.show', $.proxy(this.options.onshow, this))
+ .on('suggest.select', $.proxy(this.options.onselect, this))
+ .on('suggest.lookup', $.proxy(this.options.onlookup, this))
+ .on('keyup', $.proxy(this.__keyup, this));
+
+ return this;
+ },
+
+ __getCaretPos: function(posStart) {
+ // https://github.com/component/textarea-caret-position/blob/master/index.js
+
+ // The properties that we copy into a mirrored div.
+ // Note that some browsers, such as Firefox,
+ // do not concatenate properties, i.e. padding-top, bottom etc. -> padding,
+ // so we have to do every single property specifically.
+ var properties = [
+ 'direction', // RTL support
+ 'boxSizing',
+ 'width', // on Chrome and IE, exclude the scrollbar, so the mirror div wraps exactly as the textarea does
+ 'height',
+ 'overflowX',
+ 'overflowY', // copy the scrollbar for IE
+
+ 'borderTopWidth',
+ 'borderRightWidth',
+ 'borderBottomWidth',
+ 'borderLeftWidth',
+
+ 'paddingTop',
+ 'paddingRight',
+ 'paddingBottom',
+ 'paddingLeft',
+
+ // https://developer.mozilla.org/en-US/docs/Web/CSS/font
+ 'fontStyle',
+ 'fontVariant',
+ 'fontWeight',
+ 'fontStretch',
+ 'fontSize',
+ 'fontSizeAdjust',
+ 'lineHeight',
+ 'fontFamily',
+
+ 'textAlign',
+ 'textTransform',
+ 'textIndent',
+ 'textDecoration', // might not make a difference, but better be safe
+
+ 'letterSpacing',
+ 'wordSpacing'
+ ];
+
+ var isFirefox = !(window.mozInnerScreenX == null);
+
+ var getCaretCoordinatesFn = function (element, position, recalculate) {
+ // mirrored div
+ var div = document.createElement('div');
+ div.id = 'input-textarea-caret-position-mirror-div';
+ document.body.appendChild(div);
+
+ var style = div.style;
+ var computed = window.getComputedStyle? getComputedStyle(element) : element.currentStyle; // currentStyle for IE < 9
+
+ // default textarea styles
+ style.whiteSpace = 'pre-wrap';
+ if (element.nodeName !== 'INPUT')
+ style.wordWrap = 'break-word'; // only for textarea-s
+
+ // position off-screen
+ style.position = 'absolute'; // required to return coordinates properly
+ style.visibility = 'hidden'; // not 'display: none' because we want rendering
+
+ // transfer the element's properties to the div
+ $.each(properties, function (index, value) {
+ style[value] = computed[value];
+ });
+
+ if (isFirefox) {
+ style.width = parseInt(computed.width) - 2 + 'px'; // Firefox adds 2 pixels to the padding - https://bugzilla.mozilla.org/show_bug.cgi?id=753662
+ // Firefox lies about the overflow property for textareas: https://bugzilla.mozilla.org/show_bug.cgi?id=984275
+ if (element.scrollHeight > parseInt(computed.height))
+ style.overflowY = 'scroll';
+ } else {
+ style.overflow = 'hidden'; // for Chrome to not render a scrollbar; IE keeps overflowY = 'scroll'
+ }
+
+ div.textContent = element.value.substring(0, position);
+ // the second special handling for input type="text" vs textarea: spaces need to be replaced with non-breaking spaces - http://stackoverflow.com/a/13402035/1269037
+ if (element.nodeName === 'INPUT')
+ div.textContent = div.textContent.replace(/\s/g, "\u00a0");
+
+ var span = document.createElement('span');
+ // Wrapping must be replicated *exactly*, including when a long word gets
+ // onto the next line, with whitespace at the end of the line before (#7).
+ // The *only* reliable way to do that is to copy the *entire* rest of the
+ // textarea's content into the created at the caret position.
+ // for inputs, just '.' would be enough, but why bother?
+ span.textContent = element.value.substring(position) || '.'; // || because a completely empty faux span doesn't render at all
+ div.appendChild(span);
+
+ var coordinates = {
+ top: span.offsetTop + parseInt(computed['borderTopWidth']),
+ left: span.offsetLeft + parseInt(computed['borderLeftWidth'])
+ };
+
+ document.body.removeChild(div);
+
+ return coordinates;
+ }
+
+ return getCaretCoordinatesFn(this.$element.get(0), posStart);
+ },
+
+ __keyup: function(e) {
+ // don't query special characters
+ // http://mikemurko.com/general/jquery-keycode-cheatsheet/
+ var specialChars = [38, 40, 37, 39, 17, 18, 9, 16, 20, 91, 93, 36, 35, 45, 33, 34, 144, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 145, 19];
+
+ switch (e.keyCode) {
+ case 27: // escape
+ this.hide();
+ return;
+ case 13: // enter/return
+ return true;
+ }
+
+ if ($.inArray(e.keyCode, specialChars) !== -1) return;
+
+ var $el = this.$element,
+ val = $el.val(),
+ currentPos = this.__getSelection($el.get(0)).start;
+
+ for (var i = currentPos; i >= 0; i--) {
+ var subChar = $.trim(val.substring(i-1, i));
+ if (!subChar) {
+ this.hide();
+ break;
+ }
+
+ if (subChar === this.key && $.trim(val.substring(i-2, i-1)) == '') {
+ this.query = val.substring(i, currentPos);
+ this._queryPos = [i, currentPos];
+ this._keyPos = i;
+ this.lookup(this.query);
+ break;
+ }
+ }
+ },
+
+ __getVisibleItems: function() {
+ return this.$items ? this.$items.not('.d-none') : $();
+ },
+
+ __build: function() {
+ var elems = [], $item,
+ $dropdown = this.$dropdown,
+ that = this;
+
+ var blur = function(e) {
+ that.hide();
+ }
+
+ $dropdown
+ .on('click', 'li:has(a)', function(e) {
+ e.preventDefault();
+ that.__select($(this).index());
+ that.$element.focus();
+ })
+ .on('mouseover', 'li:has(a)', function(e) {
+ that.$element.off('blur', blur);
+ })
+ .on('mouseout', 'li:has(a)', function(e) {
+ that.$element.on('blur', blur);
+ });
+
+ this.$element.before($dropdown).on('blur', blur).on('keydown', function(e) {
+ var $visibleItems;
+ if (that.isShown) {
+ switch (e.keyCode) {
+ case 13: // enter key
+ $visibleItems = that.__getVisibleItems();
+ $visibleItems.each(function(index) {
+ if ($(this).is('.active'))
+ that.__select($(this).index());
+ });
+
+ return false;
+ break;
+ case 40: // arrow down
+ $visibleItems = that.__getVisibleItems();
+ if ($visibleItems.last().is('.active')) return false;
+ $visibleItems.each(function(index) {
+ var $this = $(this),
+ $next = $visibleItems.eq(index + 1);
+
+ //if (!$next.length) return false;
+
+ if ($this.is('.active')) {
+ if (!$next.is('.hidden')) {
+ $this.removeClass('active');
+ $next.addClass('active');
+ }
+ return false;
+ }
+ });
+ return false;
+ case 38: // arrow up
+ $visibleItems = that.__getVisibleItems();
+ if ($visibleItems.first().is('.active')) return false;
+ $visibleItems.each(function(index) {
+ var $this = $(this),
+ $prev = $visibleItems.eq(index - 1);
+
+ //if (!$prev.length) return false;
+
+ if ($this.is('.active')) {
+ if (!$prev.is('.hidden')) {
+ $this.removeClass('active');
+ $prev.addClass('active');
+ }
+ return false;
+ }
+ })
+ return false;
+ }
+ }
+ });
+
+ },
+
+ __mapItem: function(dataItem) {
+ var itemHtml, that = this,
+ _item = {
+ text: '',
+ value: ''
+ };
+
+ if (this.options.map) {
+ dataItem = this.options.map(dataItem);
+ if (!dataItem) return false;
+ }
+
+ if (dataItem instanceof Object) {
+ _item.text = dataItem.text || '';
+ _item.value = dataItem.value || '';
+ } else {
+ _item.text = dataItem;
+ _item.value = dataItem;
+ }
+
+ return $(' ', {'data-value': _item.value}).html($(' ', {
+ href: '#',
+ html: _item.text
+ })).addClass('dropdown-item');
+ },
+
+ __select: function(index) {
+ var $el = this.$element,
+ el = $el.get(0),
+ val = $el.val(),
+ item = this.get(index),
+ setCaretPos = this._keyPos + item.value.length + 1;
+
+ $el.val(val.slice(0, this._keyPos) + item.value + this.options.endKey + val.slice(this.__getSelection(el).start));
+
+ if (el.setSelectionRange) {
+ el.setSelectionRange(setCaretPos, setCaretPos);
+ } else if (el.createTextRange) {
+ var range = el.createTextRange();
+ range.collapse(true);
+ range.moveEnd('character', setCaretPos);
+ range.moveStart('character', setCaretPos);
+ range.select();
+ }
+
+ $el.trigger($.extend({type: 'suggest.select'}, this), item);
+
+ this.hide();
+ },
+
+ __getSelection: function (el) {
+ //in IE9 selectionStart will always be 9 if not focused(when selecting using the mouse)
+ el.focus();
+
+ return {
+ start: el.selectionStart,
+ end: el.selectionEnd
+ };
+ },
+
+ __buildItems: function(data) {
+ var $dropdownMenu = this.$dropdown.find('.dropdown-menu');
+
+ $dropdownMenu.empty();
+ if (data && data instanceof Array) {
+ for (var i in data) {
+ var $item = this.__mapItem(data[i]);
+ if ($item) {
+ $dropdownMenu.append($item);
+ }
+ }
+ }
+ return $dropdownMenu.find('li:has(a)');
+ },
+
+ __lookup: function(q, $resultItems) {
+ var active = $resultItems.eq(0).addClass('active');
+ this.$element.trigger($.extend({type: 'suggest.lookup'}, this), [q, $resultItems]);
+
+ if ($resultItems && $resultItems.length) {
+ this.show();
+ } else {
+ this.hide();
+ }
+ },
+
+ __filterData: function(q, data) {
+ var options = this.options;
+ this.$items.addClass('hidden');
+ this.$items.filter(function (index) {
+
+ // return the limit if q is empty
+ if (q === '') return index < options.filter.limit;
+
+ var $this = $(this),
+ value = $this.find('a:first').text();
+
+ if (!options.filter.casesensitive) {
+ value = value.toLowerCase();
+ q = q.toLowerCase();
+ }
+ return value.indexOf(q) != -1;
+ }).slice(0, options.filter.limit).removeClass('hidden active');
+ return this.__getVisibleItems();
+ },
+
+ get: function(index) {
+ if (!this.$items) return;
+
+ var $item = this.$items.eq(index);
+ return {
+ text: $item.children('a:first').text(),
+ value: $item.attr('data-value'),
+ index: index,
+ $element: $item
+ };
+ },
+
+ lookup: function(q) {
+ var options = this.options,
+ that = this,
+ data;
+
+ var provide = function(data) {
+ // verify that we're still "typing" the query (no space)
+ if (that._keyPos !== -1) {
+ if (!that.$items) {
+ that.$items = that.__buildItems(data);
+ }
+
+ that.__lookup(q, that.__filterData(q, data));
+ }
+ };
+
+ if (typeof this.options.data === 'function') {
+ this.$items = undefined;
+ data = this.options.data(q, provide);
+ } else {
+ data = this.options.data;
+ }
+
+ if (data && typeof data.promise === 'function') {
+ data.done(provide);
+ } else if (data) {
+ provide.call(this, data);
+ }
+ },
+
+ load: function() {
+ this.__setListener();
+ this.__build();
+ },
+
+ hide: function() {
+ this.$dropdown.removeClass('open show');
+ this.isShown = false;
+ if(this.$items) {
+ this.$items.removeClass('active');
+ }
+ this._keyPos = -1;
+ },
+
+ show: function() {
+ var $el = this.$element,
+ $dropdownMenu = this.$dropdown.find('.dropdown-menu'),
+ el = $el.get(0),
+ options = this.options,
+ caretPos,
+ position = {
+ top: 'auto',
+ bottom: 'auto',
+ left: 'auto',
+ right: 'auto'
+ };
+
+ if (!this.isShown) {
+
+ this.$dropdown.addClass('open show');
+ if (options.position !== false) {
+
+ caretPos = this.__getCaretPos(this._keyPos);
+
+ if (typeof options.position == 'string') {
+ switch (options.position) {
+ case 'bottom':
+ position.top = $el.outerHeight() - parseFloat($dropdownMenu.css('margin-top'));
+ position.left = 0;
+ position.right = 0;
+ break;
+ case 'top':
+ position.top = -($dropdownMenu.outerHeight(true) + parseFloat($dropdownMenu.css('margin-top')));
+ position.left = 0;
+ position.right = 0;
+ break;
+ case 'caret':
+ position.top = caretPos.top - el.scrollTop;
+ position.left = caretPos.left - el.scrollLeft;
+ break;
+ }
+
+ } else {
+ position = $.extend(position, typeof options.position === 'function' ? options.position(el, caretPos) : options.position);
+ }
+
+ $dropdownMenu.css(position);
+ }
+
+ this.isShown = true;
+ $el.trigger($.extend({type: 'suggest.show'}, this));
+ }
+ }
+ };
+
+ var old = $.fn.suggest;
+
+ // .suggest( key [, options] )
+ // .suggest( method [, options] )
+ // .suggest( suggestions )
+ $.fn.suggest = function(arg1) {
+ var arg2 = arguments[1],
+ arg3 = arguments[2];
+
+ var createSuggestions = function(el, suggestions) {
+ var newData = {};
+ $.each(suggestions, function(keyChar, options) {
+ var key = keyChar.toString().charAt(0);
+
+ // remove existing suggest
+ // $('.suggest.dropdown[data-key="'+key+'"]').remove();
+ newData[key] = new Suggest(el, key, typeof options === 'object' && options);
+ });
+
+ return newData;
+ };
+
+ return this.each(function() {
+ var that = this,
+ $this = $(this),
+ data = $this.data('suggest'),
+ suggestions = {};
+
+ if (typeof arg1 === 'string') {
+ if (arg1.length == 1) {
+ // arg1 as key
+ if (arg2) {
+ // arg2 is a function name
+ if (typeof arg2 === 'string') {
+ if (arg1 in data && typeof data[arg1][arg2] !== 'undefined') {
+ return data[arg1][arg2].call(data[arg1], arg3);
+ } else {
+ console.error(arg1 + ' is not a suggest');
+ }
+ } else {
+ // inline data determined if it's an array
+ suggestions[arg1] = $.isArray(arg2) || typeof arg2 === 'function' ? {data: arg2} : arg2;
+
+ // if key is existing, update options
+ if (data && arg1 in data) {
+ data[arg1].options = $.extend({}, data[arg1].options, suggestions[arg1]);
+ } else {
+ data = $.extend(data, createSuggestions(this, suggestions));
+ }
+
+ $this.data('suggest', data);
+ }
+ }
+ } else {
+ console.error('you\'re not initializing suggest properly. arg1 should have length == 1');
+ }
+ } else {
+ // arg1 contains set of suggestions
+ if (!data) $this.data('suggest', createSuggestions(this, arg1));
+ else if (data) {
+ // create/update suggestions
+ $.each(arg1, function(key, value) {
+ if (key in data === false) {
+ suggestions[key] = value;
+ } else {
+ // extend (update) options
+ data[key].options = $.extend({}, data[key].options, value);
+ }
+ });
+
+ $this.data('suggest', $.extend(data, createSuggestions(that, suggestions)))
+ }
+ }
+ });
+ };
+
+ $.fn.suggest.defaults = {
+ data: [],
+ map: undefined,
+ filter: {
+ casesensitive: false,
+ limit: 5
+ },
+ endKey: ' ',
+ dropdownClass: '',
+ position: 'caret',
+ // events hook
+ onshow: function(e) {},
+ onselect: function(e, item) {},
+ onlookup: function(e, item) {}
+
+ }
+
+ $.fn.suggest.Constructor = Suggest;
+
+ $.fn.suggest.noConflict = function () {
+ $.fn.suggest = old;
+ return this;
+ }
+
+}( jQuery ));
\ No newline at end of file
diff --git a/e107_web/lib/bootstrap-suggest/dist/bootstrap-suggest.min.js b/e107_web/lib/bootstrap-suggest/dist/bootstrap-suggest.min.js
new file mode 100644
index 000000000..c397a67ae
--- /dev/null
+++ b/e107_web/lib/bootstrap-suggest/dist/bootstrap-suggest.min.js
@@ -0,0 +1,31 @@
+/*!
+ * bootstra-suggest - v2.0.1 (https://github.com/lodev09/bootstrap-suggest#readme)
+ * Copyright 2013-2019 Jovanni Lo (lodev09@gmail.com)
+ * Licensed under MIT (https://github.com/lodev09/bootstrap-suggest/blob/master/LICENSE)
+*/
+
+(function($){"use strict";var Suggest=function(el,key,options){var that=this;this.$element=$(el);this.$items=undefined;this.options=$.extend(true,{},$.fn.suggest.defaults,options,this.$element.data(),this.$element.data('options'));this.key=key;this.isShown=false;this.query='';this._queryPos=[];this._keyPos=-1;this.$dropdown=$('
',{'class':'dropdown suggest '+this.options.dropdownClass,'html':$('',{'class':'dropdown-menu',role:'menu'}),'data-key':this.key});this.load();};Suggest.prototype={__setListener:function(){this.$element.on('suggest.show',$.proxy(this.options.onshow,this)).on('suggest.select',$.proxy(this.options.onselect,this)).on('suggest.lookup',$.proxy(this.options.onlookup,this)).on('keyup',$.proxy(this.__keyup,this));return this;},__getCaretPos:function(posStart){var properties=['direction','boxSizing','width','height','overflowX','overflowY','borderTopWidth','borderRightWidth','borderBottomWidth','borderLeftWidth','paddingTop','paddingRight','paddingBottom','paddingLeft','fontStyle','fontVariant','fontWeight','fontStretch','fontSize','fontSizeAdjust','lineHeight','fontFamily','textAlign','textTransform','textIndent','textDecoration','letterSpacing','wordSpacing'];var isFirefox=!(window.mozInnerScreenX==null);var getCaretCoordinatesFn=function(element,position,recalculate){var div=document.createElement('div');div.id='input-textarea-caret-position-mirror-div';document.body.appendChild(div);var style=div.style;var computed=window.getComputedStyle?getComputedStyle(element):element.currentStyle;style.whiteSpace='pre-wrap';if(element.nodeName!=='INPUT')
+style.wordWrap='break-word';style.position='absolute';style.visibility='hidden';$.each(properties,function(index,value){style[value]=computed[value];});if(isFirefox){style.width=parseInt(computed.width)-2+'px';if(element.scrollHeight>parseInt(computed.height))
+style.overflowY='scroll';}else{style.overflow='hidden';}
+div.textContent=element.value.substring(0,position);if(element.nodeName==='INPUT')
+div.textContent=div.textContent.replace(/\s/g,"\u00a0");var span=document.createElement('span');span.textContent=element.value.substring(position)||'.';div.appendChild(span);var coordinates={top:span.offsetTop+parseInt(computed['borderTopWidth']),left:span.offsetLeft+parseInt(computed['borderLeftWidth'])};document.body.removeChild(div);return coordinates;}
+return getCaretCoordinatesFn(this.$element.get(0),posStart);},__keyup:function(e){var specialChars=[38,40,37,39,17,18,9,16,20,91,93,36,35,45,33,34,144,112,113,114,115,116,117,118,119,120,121,122,123,145,19];switch(e.keyCode){case 27:this.hide();return;case 13:return true;}
+if($.inArray(e.keyCode,specialChars)!==-1)return;var $el=this.$element,val=$el.val(),currentPos=this.__getSelection($el.get(0)).start;for(var i=currentPos;i>=0;i--){var subChar=$.trim(val.substring(i-1,i));if(!subChar){this.hide();break;}
+if(subChar===this.key&&$.trim(val.substring(i-2,i-1))==''){this.query=val.substring(i,currentPos);this._queryPos=[i,currentPos];this._keyPos=i;this.lookup(this.query);break;}}},__getVisibleItems:function(){return this.$items?this.$items.not('.d-none'):$();},__build:function(){var elems=[],$item,$dropdown=this.$dropdown,that=this;var blur=function(e){that.hide();}
+$dropdown.on('click','li:has(a)',function(e){e.preventDefault();that.__select($(this).index());that.$element.focus();}).on('mouseover','li:has(a)',function(e){that.$element.off('blur',blur);}).on('mouseout','li:has(a)',function(e){that.$element.on('blur',blur);});this.$element.before($dropdown).on('blur',blur).on('keydown',function(e){var $visibleItems;if(that.isShown){switch(e.keyCode){case 13:$visibleItems=that.__getVisibleItems();$visibleItems.each(function(index){if($(this).is('.active'))
+that.__select($(this).index());});return false;break;case 40:$visibleItems=that.__getVisibleItems();if($visibleItems.last().is('.active'))return false;$visibleItems.each(function(index){var $this=$(this),$next=$visibleItems.eq(index+1);if($this.is('.active')){if(!$next.is('.hidden')){$this.removeClass('active');$next.addClass('active');}
+return false;}});return false;case 38:$visibleItems=that.__getVisibleItems();if($visibleItems.first().is('.active'))return false;$visibleItems.each(function(index){var $this=$(this),$prev=$visibleItems.eq(index-1);if($this.is('.active')){if(!$prev.is('.hidden')){$this.removeClass('active');$prev.addClass('active');}
+return false;}})
+return false;}}});},__mapItem:function(dataItem){var itemHtml,that=this,_item={text:'',value:''};if(this.options.map){dataItem=this.options.map(dataItem);if(!dataItem)return false;}
+if(dataItem instanceof Object){_item.text=dataItem.text||'';_item.value=dataItem.value||'';}else{_item.text=dataItem;_item.value=dataItem;}
+return $(' ',{'data-value':_item.value}).html($(' ',{href:'#',html:_item.text})).addClass('dropdown-item');},__select:function(index){var $el=this.$element,el=$el.get(0),val=$el.val(),item=this.get(index),setCaretPos=this._keyPos+item.value.length+1;$el.val(val.slice(0,this._keyPos)+item.value+this.options.endKey+val.slice(this.__getSelection(el).start));if(el.setSelectionRange){el.setSelectionRange(setCaretPos,setCaretPos);}else if(el.createTextRange){var range=el.createTextRange();range.collapse(true);range.moveEnd('character',setCaretPos);range.moveStart('character',setCaretPos);range.select();}
+$el.trigger($.extend({type:'suggest.select'},this),item);this.hide();},__getSelection:function(el){el.focus();return{start:el.selectionStart,end:el.selectionEnd};},__buildItems:function(data){var $dropdownMenu=this.$dropdown.find('.dropdown-menu');$dropdownMenu.empty();if(data&&data instanceof Array){for(var i in data){var $item=this.__mapItem(data[i]);if($item){$dropdownMenu.append($item);}}}
+return $dropdownMenu.find('li:has(a)');},__lookup:function(q,$resultItems){var active=$resultItems.eq(0).addClass('active');this.$element.trigger($.extend({type:'suggest.lookup'},this),[q,$resultItems]);if($resultItems&&$resultItems.length){this.show();}else{this.hide();}},__filterData:function(q,data){var options=this.options;this.$items.addClass('hidden');this.$items.filter(function(index){if(q==='')return index