diff --git a/oioioi/base/static/js/bootstrap-typeahead.js b/oioioi/base/static/js/bootstrap-typeahead.js index c1f0abedc..b26b84b77 100644 --- a/oioioi/base/static/js/bootstrap-typeahead.js +++ b/oioioi/base/static/js/bootstrap-typeahead.js @@ -1,4 +1,3 @@ -/* jshint ignore:start */ /* ============================================================= * bootstrap3-typeahead.js v4.0.2 * https://github.com/bassjobsen/Bootstrap-3-Typeahead @@ -23,585 +22,753 @@ (function (root, factory) { - 'use strict'; + 'use strict'; - // CommonJS module is defined - if (typeof module !== 'undefined' && module.exports) { - module.exports = factory(require('jquery')); - } - // AMD module is defined - else if (typeof define === 'function' && define.amd) { - define(['jquery'], function ($) { - return factory ($); - }); - } else { - factory(root.jQuery); - } + // CommonJS module is defined + if (typeof module !== 'undefined' && module.exports) { + module.exports = factory(require('jquery')); + } + // AMD module is defined + else if (typeof define === 'function' && define.amd) { + define(['jquery'], function ($) { + return factory($); + }); + } else { + factory(root.jQuery); + } }(this, function ($) { - 'use strict'; - // jshint laxcomma: true - - - /* TYPEAHEAD PUBLIC CLASS DEFINITION - * ================================= */ - - var Typeahead = function (element, options) { - this.$element = $(element); - this.options = $.extend({}, Typeahead.defaults, options); - this.matcher = this.options.matcher || this.matcher; - this.sorter = this.options.sorter || this.sorter; - this.select = this.options.select || this.select; - this.autoSelect = typeof this.options.autoSelect == 'boolean' ? this.options.autoSelect : true; - this.highlighter = this.options.highlighter || this.highlighter; - this.render = this.options.render || this.render; - this.updater = this.options.updater || this.updater; - this.displayText = this.options.displayText || this.displayText; - this.source = this.options.source; - this.delay = this.options.delay; - this.$menu = $(this.options.menu); - this.$appendTo = this.options.appendTo ? $(this.options.appendTo) : null; - this.fitToElement = typeof this.options.fitToElement == 'boolean' ? this.options.fitToElement : false; - this.shown = false; - this.listen(); - this.showHintOnFocus = typeof this.options.showHintOnFocus == 'boolean' || this.options.showHintOnFocus === "all" ? this.options.showHintOnFocus : false; - this.afterSelect = this.options.afterSelect; - this.addItem = false; - this.value = this.$element.val() || this.$element.text(); - this.keyPressed = false; - }; - - Typeahead.prototype = { - - constructor: Typeahead, - - select: function () { - var val = this.$menu.find('.active').data('value'); - this.$element.data('active', val); - if (this.autoSelect || val) { - var newVal = this.updater(val); - // Updater can be set to any random functions via "options" parameter in constructor above. - // Add null check for cases when updater returns void or undefined. - if (!newVal) { - newVal = ''; - } - this.$element - .val(this.displayText(newVal) || newVal) - .text(this.displayText(newVal) || newVal) - .change(); - this.afterSelect(newVal); - } - return this.hide(); - }, - - updater: function (item) { - return item; - }, - - setSource: function (source) { - this.source = source; - }, - - show: function () { - var pos = $.extend({}, this.$element.position(), { - height: this.$element[0].offsetHeight - }); - - var scrollHeight = typeof this.options.scrollHeight == 'function' ? - this.options.scrollHeight.call() : - this.options.scrollHeight; - - var element; - if (this.shown) { - element = this.$menu; - } else if (this.$appendTo) { - element = this.$menu.appendTo(this.$appendTo); - this.hasSameParent = this.$appendTo.is(this.$element.parent()); - } else { - element = this.$menu.insertAfter(this.$element); - this.hasSameParent = true; - } - - if (!this.hasSameParent) { - // We cannot rely on the element position, need to position relative to the window - element.css("position", "fixed"); - var offset = this.$element.offset(); - pos.top = offset.top; - pos.left = offset.left; - } - // The rules for bootstrap are: 'dropup' in the parent and 'dropdown-menu-right' in the element. - // Note that to get right alignment, you'll need to specify `menu` in the options to be: - // '' - var dropup = $(element).parent().hasClass('dropup'); - var newTop = dropup ? 'auto' : (pos.top + pos.height + scrollHeight); - var right = $(element).hasClass('dropdown-menu-right'); - var newLeft = right ? 'auto' : pos.left; - // it seems like setting the css is a bad idea (just let Bootstrap do it), but I'll keep the old - // logic in place except for the dropup/right-align cases. - element.css({ top: newTop, left: newLeft }).show(); - - if (this.options.fitToElement === true) { - element.css("width", this.$element.outerWidth() + "px"); - } - - this.shown = true; - return this; - }, - - hide: function () { - this.$menu.hide(); - this.shown = false; - return this; - }, - - lookup: function (query) { - var items; - if (typeof(query) != 'undefined' && query !== null) { - this.query = query; - } else { - this.query = this.$element.val() || this.$element.text() || ''; - } - - if (this.query.length < this.options.minLength && !this.options.showHintOnFocus) { - return this.shown ? this.hide() : this; - } - - var worker = $.proxy(function () { - - // Bloodhound (since 0.11) needs three arguments. - // Two of them are callback functions (sync and async) for local and remote data processing - // see https://github.com/twitter/typeahead.js/blob/master/src/bloodhound/bloodhound.js#L132 - if ($.isFunction(this.source) && this.source.length === 3) { - this.source(this.query, $.proxy(this.process, this), $.proxy(this.process, this)); - } else if ($.isFunction(this.source)) { - this.source(this.query, $.proxy(this.process, this)); - } else if (this.source) { - this.process(this.source); - } - }, this); - - clearTimeout(this.lookupWorker); - this.lookupWorker = setTimeout(worker, this.delay); - }, - - process: function (items) { - var that = this; - - items = $.grep(items, function (item) { - return that.matcher(item); - }); - - items = this.sorter(items); - - if (!items.length && !this.options.addItem) { - return this.shown ? this.hide() : this; - } - - if (items.length > 0) { - this.$element.data('active', items[0]); - } else { - this.$element.data('active', null); - } - - if (this.options.items != 'all') { - items = items.slice(0, this.options.items); - } - - // Add item - if (this.options.addItem){ - items.push(this.options.addItem); - } - - return this.render(items).show(); - }, - - matcher: function (item) { - var it = this.displayText(item); - return ~it.toLowerCase().indexOf(this.query.toLowerCase()); - }, - - sorter: function (items) { - var beginswith = []; - var caseSensitive = []; - var caseInsensitive = []; - var item; - - while ((item = items.shift())) { - var it = this.displayText(item); - if (!it.toLowerCase().indexOf(this.query.toLowerCase())) beginswith.push(item); - else if (~it.indexOf(this.query)) caseSensitive.push(item); - else caseInsensitive.push(item); - } - - return beginswith.concat(caseSensitive, caseInsensitive); - }, - - highlighter: function (item) { - var text = this.query; - if(text===""){ - return item; - } - var matches = item.match(/(>)([^<]*)(<)/g); - var first = []; - var second = []; - var i; - if(matches && matches.length){ - //html - for (i = 0; i < matches.length; ++i) { - if (matches[i].length > 2) {//escape '><' - first.push(matches[i]); - } - } - }else{ - //text - first = []; - first.push(item); - } - - var reg = new RegExp(text, "g"); - var m; - for (i = 0; i < first.length; ++i) { - m = first[i].match(reg); - if(m && m.length>0){//find all text nodes matches - second.push(first[i]); - } - } - for (i = 0; i < second.length; ++i) { - item = item.replace(second[i],second[i].replace(reg, '$&')); - } - return item; - }, - - render: function (items) { - var that = this; - var self = this; - var activeFound = false; - var data = []; - var _category = that.options.separator; - - $.each(items, function (key,value) { - // inject separator - if (key > 0 && value[_category] !== items[key - 1][_category]){ - data.push({ - __type: 'divider' - }); + 'use strict'; + // jshint laxcomma: true + + + /* TYPEAHEAD PUBLIC CLASS DEFINITION + * ================================= */ + + var Typeahead = function (element, options) { + this.$element = $(element); + this.options = $.extend({}, Typeahead.defaults, options); + this.matcher = this.options.matcher || this.matcher; + this.sorter = this.options.sorter || this.sorter; + this.select = this.options.select || this.select; + this.autoSelect = typeof this.options.autoSelect == 'boolean' ? this.options.autoSelect : true; + this.highlighter = this.options.highlighter || this.highlighter; + this.render = this.options.render || this.render; + this.updater = this.options.updater || this.updater; + this.displayText = this.options.displayText || this.displayText; + this.itemLink = this.options.itemLink || this.itemLink; + this.itemTitle = this.options.itemTitle || this.itemTitle; + this.followLinkOnSelect = this.options.followLinkOnSelect || this.followLinkOnSelect; + this.source = this.options.source; + this.delay = this.options.delay; + this.theme = this.options.theme && this.options.themes && this.options.themes[this.options.theme] || Typeahead.defaults.themes[Typeahead.defaults.theme]; + this.$menu = $(this.options.menu || this.theme.menu); + this.$appendTo = this.options.appendTo ? $(this.options.appendTo) : null; + this.fitToElement = typeof this.options.fitToElement == 'boolean' ? this.options.fitToElement : false; + this.shown = false; + this.listen(); + this.showHintOnFocus = typeof this.options.showHintOnFocus == 'boolean' || this.options.showHintOnFocus === 'all' ? this.options.showHintOnFocus : false; + this.afterSelect = this.options.afterSelect; + this.afterEmptySelect = this.options.afterEmptySelect; + this.addItem = false; + this.value = this.$element.val() || this.$element.text(); + this.keyPressed = false; + this.focused = this.$element.is(':focus'); + this.changeInputOnSelect = this.options.changeInputOnSelect || this.changeInputOnSelect; + this.changeInputOnMove = this.options.changeInputOnMove || this.changeInputOnMove; + this.openLinkInNewTab = this.options.openLinkInNewTab || this.openLinkInNewTab; + this.selectOnBlur = this.options.selectOnBlur || this.selectOnBlur; + this.showCategoryHeader = this.options.showCategoryHeader || this.showCategoryHeader; + }; + + Typeahead.prototype = { + + constructor: Typeahead, + + + setDefault: function (val) { + // var val = this.$menu.find('.active').data('value'); + this.$element.data('active', val); + if (this.autoSelect || val) { + var newVal = this.updater(val); + // Updater can be set to any random functions via "options" parameter in constructor above. + // Add null check for cases when updater returns void or undefined. + if (!newVal) { + newVal = ''; + } + this.$element + .val(this.displayText(newVal) || newVal) + .text(this.displayText(newVal) || newVal) + .change(); + this.afterSelect(newVal); + } + return this.hide(); + }, + + select: function () { + var val = this.$menu.find('.active').data('value'); + + this.$element.data('active', val); + if (this.autoSelect || val) { + var newVal = this.updater(val); + // Updater can be set to any random functions via "options" parameter in constructor above. + // Add null check for cases when updater returns void or undefined. + if (!newVal) { + newVal = ''; + } + + if (this.changeInputOnSelect) { + this.$element + .val(this.displayText(newVal) || newVal) + .text(this.displayText(newVal) || newVal) + .change(); + } + + if (this.followLinkOnSelect && this.itemLink(val)) { + if (this.openLinkInNewTab) { + window.open(this.itemLink(val), '_blank'); + } else { + document.location = this.itemLink(val); + } + this.afterSelect(newVal); + } else if (this.followLinkOnSelect && !this.itemLink(val)) { + this.afterEmptySelect(newVal); + } else { + this.afterSelect(newVal); + } + } else { + this.afterEmptySelect(); + } + + return this.hide(); + }, + + updater: function (item) { + return item; + }, + + setSource: function (source) { + this.source = source; + }, + + show: function () { + var pos = $.extend({}, this.$element.position(), { + height: this.$element[0].offsetHeight + }); + + var scrollHeight = typeof this.options.scrollHeight == 'function' ? + this.options.scrollHeight.call() : + this.options.scrollHeight; + + var element; + if (this.shown) { + element = this.$menu; + } else if (this.$appendTo) { + element = this.$menu.appendTo(this.$appendTo); + this.hasSameParent = this.$appendTo.is(this.$element.parent()); + } else { + element = this.$menu.insertAfter(this.$element); + this.hasSameParent = true; + } + + if (!this.hasSameParent) { + // We cannot rely on the element position, need to position relative to the window + element.css('position', 'fixed'); + var offset = this.$element.offset(); + pos.top = offset.top; + pos.left = offset.left; + } + // The rules for bootstrap are: 'dropup' in the parent and 'dropdown-menu-right' in the element. + // Note that to get right alignment, you'll need to specify `menu` in the options to be: + // '' + var dropup = $(element).parent().hasClass('dropup'); + var newTop = dropup ? 'auto' : (pos.top + pos.height + scrollHeight); + var right = $(element).hasClass('dropdown-menu-right'); + var newLeft = right ? 'auto' : pos.left; + // it seems like setting the css is a bad idea (just let Bootstrap do it), but I'll keep the old + // logic in place except for the dropup/right-align cases. + element.css({ top: newTop, left: newLeft }).show(); + + if (this.options.fitToElement === true) { + element.css('width', this.$element.outerWidth() + 'px'); + } + + this.shown = true; + return this; + }, + + hide: function () { + this.$menu.hide(); + this.shown = false; + return this; + }, + + lookup: function (query) { + if (typeof(query) != 'undefined' && query !== null) { + this.query = query; + } else { + this.query = this.$element.val(); + } + + if (this.query.length < this.options.minLength && !this.options.showHintOnFocus) { + return this.shown ? this.hide() : this; + } + + var worker = $.proxy(function () { + + // Bloodhound (since 0.11) needs three arguments. + // Two of them are callback functions (sync and async) for local and remote data processing + // see https://github.com/twitter/typeahead.js/blob/master/src/bloodhound/bloodhound.js#L132 + if ($.isFunction(this.source) && this.source.length === 3) { + this.source(this.query, $.proxy(this.process, this), $.proxy(this.process, this)); + } else if ($.isFunction(this.source)) { + this.source(this.query, $.proxy(this.process, this)); + } else if (this.source) { + this.process(this.source); + } + }, this); + + clearTimeout(this.lookupWorker); + this.lookupWorker = setTimeout(worker, this.delay); + }, + + process: function (items) { + var that = this; + + items = $.grep(items, function (item) { + return that.matcher(item); + }); + + items = this.sorter(items); + + if (!items.length && !this.options.addItem) { + return this.shown ? this.hide() : this; + } + + if (items.length > 0) { + this.$element.data('active', items[0]); + } else { + this.$element.data('active', null); + } + + if (this.options.items != 'all') { + items = items.slice(0, this.options.items); + } + + // Add item + if (this.options.addItem) { + items.push(this.options.addItem); + } + + return this.render(items).show(); + }, + + matcher: function (item) { + var it = this.displayText(item); + return ~it.toLowerCase().indexOf(this.query.toLowerCase()); + }, + + sorter: function (items) { + var beginswith = []; + var caseSensitive = []; + var caseInsensitive = []; + var item; + + while ((item = items.shift())) { + var it = this.displayText(item); + if (!it.toLowerCase().indexOf(this.query.toLowerCase())) { + beginswith.push(item); + } else if (~it.indexOf(this.query)) { + caseSensitive.push(item); + } else { + caseInsensitive.push(item); + } + } + + return beginswith.concat(caseSensitive, caseInsensitive); + }, + + highlighter: function (item) { + var text = this.query; + if (text === '') { + return item; + } + var matches = item.match(/(>)([^<]*)(<)/g); + var first = []; + var second = []; + var i; + if (matches && matches.length) { + // html + for (i = 0; i < matches.length; ++i) { + if (matches[i].length > 2) {// escape '><' + first.push(matches[i]); + } + } + } else { + // text + first = []; + first.push(item); + } + text = text.replace((/[\(\)\/\.\*\+\?\[\]]/g), function (mat) { + return '\\' + mat; + }); + var reg = new RegExp(text, 'g'); + var m; + for (i = 0; i < first.length; ++i) { + m = first[i].match(reg); + if (m && m.length > 0) {// find all text nodes matches + second.push(first[i]); + } + } + for (i = 0; i < second.length; ++i) { + item = item.replace(second[i], second[i].replace(reg, '$&')); + } + return item; + }, + + render: function (items) { + var that = this; + var self = this; + var activeFound = false; + var data = []; + var _category = that.options.separator; + + $.each(items, function (key, value) { + // inject separator + if (key > 0 && value[_category] !== items[key - 1][_category]) { + data.push({ + __type: 'divider' + }); + } + + if (this.showCategoryHeader) { + // inject category header + if (value[_category] && (key === 0 || value[_category] !== items[key - 1][_category])) { + data.push({ + __type: 'category', + name: value[_category] + }); + } + } + + data.push(value); + }); + + items = $(data).map(function (i, item) { + if ((item.__type || false) == 'category'){ + return $(that.options.headerHtml || that.theme.headerHtml).text(item.name)[0]; + } + + if ((item.__type || false) == 'divider'){ + return $(that.options.headerDivider || that.theme.headerDivider)[0]; + } + + var text = self.displayText(item); + i = $(that.options.item || that.theme.item).data('value', item); + i.find(that.options.itemContentSelector || that.theme.itemContentSelector) + .addBack(that.options.itemContentSelector || that.theme.itemContentSelector) + .html(that.highlighter(text, item)); + if(that.options.followLinkOnSelect) { + i.find('a').attr('href', self.itemLink(item)); + } + i.find('a').attr('title', self.itemTitle(item)); + if (text == self.$element.val()) { + i.addClass('active'); + self.$element.data('active', item); + activeFound = true; + } + return i[0]; + }); + + if (this.autoSelect && !activeFound) { + items.filter(':not(.dropdown-header)').first().addClass('active'); + this.$element.data('active', items.first().data('value')); + } + this.$menu.html(items); + return this; + }, + + displayText: function (item) { + return typeof item !== 'undefined' && typeof item.name != 'undefined' ? item.name : item; + }, + + itemLink: function (item) { + return null; + }, + + itemTitle: function (item) { + return null; + }, + + next: function (event) { + var active = this.$menu.find('.active').removeClass('active'); + var next = active.next(); + + if (!next.length) { + next = $(this.$menu.find($(this.options.item || this.theme.item).prop('tagName'))[0]); + } + + while (next.hasClass('divider') || next.hasClass('dropdown-header')) { + next = next.next(); + } + + next.addClass('active'); + // added for screen reader + var newVal = this.updater(next.data('value')); + if (this.changeInputOnMove) { + this.$element.val(this.displayText(newVal) || newVal); + } + }, + + prev: function (event) { + var active = this.$menu.find('.active').removeClass('active'); + var prev = active.prev(); + + if (!prev.length) { + prev = this.$menu.find($(this.options.item || this.theme.item).prop('tagName')).last(); + } + + while (prev.hasClass('divider') || prev.hasClass('dropdown-header')) { + prev = prev.prev(); + } + + prev.addClass('active'); + // added for screen reader + var newVal = this.updater(prev.data('value')); + if (this.changeInputOnMove) { + this.$element.val(this.displayText(newVal) || newVal); + } + }, + + listen: function () { + this.$element + .on('focus.bootstrap3Typeahead', $.proxy(this.focus, this)) + .on('blur.bootstrap3Typeahead', $.proxy(this.blur, this)) + .on('keypress.bootstrap3Typeahead', $.proxy(this.keypress, this)) + .on('propertychange.bootstrap3Typeahead input.bootstrap3Typeahead', $.proxy(this.input, this)) + .on('keyup.bootstrap3Typeahead', $.proxy(this.keyup, this)); + + if (this.eventSupported('keydown')) { + this.$element.on('keydown.bootstrap3Typeahead', $.proxy(this.keydown, this)); + } + + var itemTagName = $(this.options.item || this.theme.item).prop('tagName'); + if ('ontouchstart' in document.documentElement && 'onmousemove' in document.documentElement) { + this.$menu + .on('touchstart', itemTagName, $.proxy(this.touchstart, this)) + .on('touchend', itemTagName, $.proxy(this.click, this)) + .on('click', $.proxy(this.click, this)) + .on('mouseenter', itemTagName, $.proxy(this.mouseenter, this)) + .on('mouseleave', itemTagName, $.proxy(this.mouseleave, this)) + .on('mousedown', $.proxy(this.mousedown,this)); + } else if ('ontouchstart' in document.documentElement) { + this.$menu + .on('touchstart', itemTagName, $.proxy(this.touchstart, this)) + .on('touchend', itemTagName, $.proxy(this.click, this)); + } else { + this.$menu + .on('click', $.proxy(this.click, this)) + .on('mouseenter', itemTagName, $.proxy(this.mouseenter, this)) + .on('mouseleave', itemTagName, $.proxy(this.mouseleave, this)) + .on('mousedown', $.proxy(this.mousedown, this)); + } + }, + + destroy: function () { + this.$element.data('typeahead', null); + this.$element.data('active', null); + this.$element + .unbind('focus.bootstrap3Typeahead') + .unbind('blur.bootstrap3Typeahead') + .unbind('keypress.bootstrap3Typeahead') + .unbind('propertychange.bootstrap3Typeahead input.bootstrap3Typeahead') + .unbind('keyup.bootstrap3Typeahead'); + + if (this.eventSupported('keydown')) { + this.$element.unbind('keydown.bootstrap3-typeahead'); + } + + this.$menu.remove(); + this.destroyed = true; + }, + + eventSupported: function (eventName) { + var isSupported = eventName in this.$element; + if (!isSupported) { + this.$element.setAttribute(eventName, 'return;'); + isSupported = typeof this.$element[eventName] === 'function'; + } + return isSupported; + }, + + move: function (e) { + if (!this.shown) { + return; + } + + switch (e.keyCode) { + case 9: // tab + case 13: // enter + case 27: // escape + e.preventDefault(); + break; + + case 38: // up arrow + // with the shiftKey (this is actually the left parenthesis) + if (e.shiftKey) { + return; + } + e.preventDefault(); + this.prev(); + break; + + case 40: // down arrow + // with the shiftKey (this is actually the right parenthesis) + if (e.shiftKey) { + return; + } + e.preventDefault(); + this.next(); + break; + } + }, + + keydown: function (e) { + /** + * Prevent to make an ajax call while copying and pasting. + * + * @author Simone Sacchi + * @version 2018/01/18 + */ + if (e.keyCode === 17) { // ctrl + return; + } + this.keyPressed = true; + this.suppressKeyPressRepeat = ~$.inArray(e.keyCode, [40, 38, 9, 13, 27]); + if (!this.shown && e.keyCode == 40) { + this.lookup(); + } else { + this.move(e); + } + }, + + keypress: function (e) { + if (this.suppressKeyPressRepeat) { + return; + } + this.move(e); + }, + + input: function (e) { + // This is a fixed for IE10/11 that fires the input event when a placehoder is changed + // (https://connect.microsoft.com/IE/feedback/details/810538/ie-11-fires-input-event-on-focus) + var currentValue = this.$element.val() || this.$element.text(); + if (this.value !== currentValue) { + this.value = currentValue; + this.lookup(); + } + }, + + keyup: function (e) { + if (this.destroyed) { + return; + } + switch (e.keyCode) { + case 40: // down arrow + case 38: // up arrow + case 16: // shift + case 17: // ctrl + case 18: // alt + break; + + case 9: // tab + if (!this.shown || (this.showHintOnFocus && !this.keyPressed)) { + return; + } + this.select(); + break; + case 13: // enter + if (!this.shown) { + return; + } + this.select(); + break; + + case 27: // escape + if (!this.shown) { + return; + } + this.hide(); + break; + } + + }, + + focus: function (e) { + if (!this.focused) { + this.focused = true; + this.keyPressed = false; + if (this.options.showHintOnFocus && this.skipShowHintOnFocus !== true) { + if (this.options.showHintOnFocus === 'all') { + this.lookup(''); + } else { + this.lookup(); + } + } + } + if (this.skipShowHintOnFocus) { + this.skipShowHintOnFocus = false; + } + }, + + blur: function (e) { + if (!this.mousedover && !this.mouseddown && this.shown) { + if (this.selectOnBlur) { + this.select(); + } + this.hide(); + this.focused = false; + this.keyPressed = false; + } else if (this.mouseddown) { + // This is for IE that blurs the input when user clicks on scroll. + // We set the focus back on the input and prevent the lookup to occur again + this.skipShowHintOnFocus = true; + this.$element.focus(); + this.mouseddown = false; + } + }, + + click: function (e) { + e.preventDefault(); + this.skipShowHintOnFocus = true; + this.select(); + this.$element.focus(); + this.hide(); + }, + + mouseenter: function (e) { + this.mousedover = true; + this.$menu.find('.active').removeClass('active'); + $(e.currentTarget).addClass('active'); + }, + + mouseleave: function (e) { + this.mousedover = false; + if (!this.focused && this.shown) { + this.hide(); + } + }, + + /** + * We track the mousedown for IE. When clicking on the menu scrollbar, IE makes the input blur thus hiding the menu. + */ + mousedown: function (e) { + this.mouseddown = true; + this.$menu.one('mouseup', function (e) { + // IE won't fire this, but FF and Chrome will so we reset our flag for them here + this.mouseddown = false; + }.bind(this)); + }, + + touchstart: function (e) { + e.preventDefault(); + this.$menu.find('.active').removeClass('active'); + $(e.currentTarget).addClass('active'); + }, + + touchend: function (e) { + e.preventDefault(); + this.select(); + this.$element.focus(); } - // inject category header - if (value[_category] && (key === 0 || value[_category] !== items[key - 1][_category])){ - data.push({ - __type: 'category', - name: value[_category] - }); - } - data.push(value); - }); + }; - items = $(data).map(function (i, item) { - if ((item.__type || false) == 'category'){ - return $(that.options.headerHtml).text(item.name)[0]; - } - if ((item.__type || false) == 'divider'){ - return $(that.options.headerDivider)[0]; - } + /* TYPEAHEAD PLUGIN DEFINITION + * =========================== */ + + var old = $.fn.typeahead; - var text = self.displayText(item); - i = $(that.options.item).data('value', item); - i.find('a').html(that.highlighter(text, item)); - if (text == self.$element.val()) { - i.addClass('active'); - self.$element.data('active', item); - activeFound = true; + $.fn.typeahead = function (option) { + var arg = arguments; + if (typeof option == 'string' && option == 'getActive') { + return this.data('active'); } - return i[0]; - }); - - if (this.autoSelect && !activeFound) { - items.filter(':not(.dropdown-header)').first().addClass('active'); - this.$element.data('active', items.first().data('value')); - } - this.$menu.html(items); - return this; - }, - - displayText: function (item) { - return typeof item !== 'undefined' && typeof item.name != 'undefined' ? item.name : item; - }, - - next: function (event) { - var active = this.$menu.find('.active').removeClass('active'); - var next = active.next(); - - if (!next.length) { - next = $(this.$menu.find('li')[0]); - } - - next.addClass('active'); - }, - - prev: function (event) { - var active = this.$menu.find('.active').removeClass('active'); - var prev = active.prev(); - - if (!prev.length) { - prev = this.$menu.find('li').last(); - } - - prev.addClass('active'); - }, - - listen: function () { - this.$element - .on('focus', $.proxy(this.focus, this)) - .on('blur', $.proxy(this.blur, this)) - .on('keypress', $.proxy(this.keypress, this)) - .on('propertychange input', $.proxy(this.input, this)) - .on('keyup', $.proxy(this.keyup, this)); - - if (this.eventSupported('keydown')) { - this.$element.on('keydown', $.proxy(this.keydown, this)); - } - - this.$menu - .on('click', $.proxy(this.click, this)) - .on('mouseenter', 'li', $.proxy(this.mouseenter, this)) - .on('mouseleave', 'li', $.proxy(this.mouseleave, this)) - .on('mousedown', $.proxy(this.mousedown,this)); - }, - - destroy : function () { - this.$element.data('typeahead',null); - this.$element.data('active',null); - this.$element - .off('focus') - .off('blur') - .off('keypress') - .off('propertychange input') - .off('keyup'); - - if (this.eventSupported('keydown')) { - this.$element.off('keydown'); - } - - this.$menu.remove(); - this.destroyed = true; - }, - - eventSupported: function (eventName) { - var isSupported = eventName in this.$element; - if (!isSupported) { - this.$element.setAttribute(eventName, 'return;'); - isSupported = typeof this.$element[eventName] === 'function'; - } - return isSupported; - }, - - move: function (e) { - if (!this.shown) return; - - switch (e.keyCode) { - case 9: // tab - case 13: // enter - case 27: // escape - e.preventDefault(); - break; - - case 38: // up arrow - // with the shiftKey (this is actually the left parenthesis) - if (e.shiftKey) return; - e.preventDefault(); - this.prev(); - break; - - case 40: // down arrow - // with the shiftKey (this is actually the right parenthesis) - if (e.shiftKey) return; - e.preventDefault(); - this.next(); - break; - } - }, - - keydown: function (e) { - this.keyPressed = true; - this.suppressKeyPressRepeat = ~$.inArray(e.keyCode, [40,38,9,13,27]); - if (!this.shown && e.keyCode == 40) { - this.lookup(); - } else { - this.move(e); - } - }, - - keypress: function (e) { - if (this.suppressKeyPressRepeat) return; - this.move(e); - }, - - input: function (e) { - // This is a fixed for IE10/11 that fires the input event when a placehoder is changed - // (https://connect.microsoft.com/IE/feedback/details/810538/ie-11-fires-input-event-on-focus) - var currentValue = this.$element.val() || this.$element.text(); - if (this.value !== currentValue) { - this.value = currentValue; - this.lookup(); - } - }, - - keyup: function (e) { - if (this.destroyed) { - return; - } - switch (e.keyCode) { - case 40: // down arrow - case 38: // up arrow - case 16: // shift - case 17: // ctrl - case 18: // alt - break; - - case 9: // tab - if (!this.shown || (this.showHintOnFocus && !this.keyPressed)) return; - this.select(); - break; - case 13: // enter - if (!this.shown) return; - this.select(); - break; - - case 27: // escape - if (!this.shown) return; - this.hide(); - break; - } - - }, - - focus: function (e) { - if (!this.focused) { - this.focused = true; - this.keyPressed = false; - if (this.options.showHintOnFocus && this.skipShowHintOnFocus !== true) { - if(this.options.showHintOnFocus === "all") { - this.lookup(""); - } else { - this.lookup(); - } + return this.each(function () { + var $this = $(this); + var data = $this.data('typeahead'); + var options = typeof option == 'object' && option; + if (!data) { + $this.data('typeahead', (data = new Typeahead(this, options))); + } + if (typeof option == 'string' && data[option]) { + if (arg.length > 1) { + data[option].apply(data, Array.prototype.slice.call(arg, 1)); + } else { + data[option](); + } + } + }); + }; + + Typeahead.defaults = { + source: [], + items: 8, + minLength: 1, + scrollHeight: 0, + autoSelect: true, + afterSelect: $.noop, + afterEmptySelect: $.noop, + addItem: false, + followLinkOnSelect: false, + delay: 0, + separator: 'category', + changeInputOnSelect: true, + changeInputOnMove: true, + openLinkInNewTab: false, + selectOnBlur: true, + showCategoryHeader: true, + theme: "bootstrap3", + themes: { + bootstrap3: { + menu: '', + item: '
  • ', + itemContentSelector: "a", + headerHtml: '', + headerDivider: '' + }, + bootstrap4: { + menu: '', + item: '', + itemContentSelector: '.dropdown-item', + headerHtml: '', + headerDivider: '' } - } - if (this.skipShowHintOnFocus) { - this.skipShowHintOnFocus = false; - } - }, - - blur: function (e) { - if (!this.mousedover && !this.mouseddown && this.shown) { - this.hide(); - this.focused = false; - this.keyPressed = false; - } else if (this.mouseddown) { - // This is for IE that blurs the input when user clicks on scroll. - // We set the focus back on the input and prevent the lookup to occur again - this.skipShowHintOnFocus = true; - this.$element.focus(); - this.mouseddown = false; - } - }, - - click: function (e) { - e.preventDefault(); - this.skipShowHintOnFocus = true; - this.select(); - this.$element.focus(); - this.hide(); - }, - - mouseenter: function (e) { - this.mousedover = true; - this.$menu.find('.active').removeClass('active'); - $(e.currentTarget).addClass('active'); - }, - - mouseleave: function (e) { - this.mousedover = false; - if (!this.focused && this.shown) this.hide(); - }, - - /** - * We track the mousedown for IE. When clicking on the menu scrollbar, IE makes the input blur thus hiding the menu. - */ - mousedown: function (e) { - this.mouseddown = true; - this.$menu.one("mouseup", function(e){ - // IE won't fire this, but FF and Chrome will so we reset our flag for them here - this.mouseddown = false; - }.bind(this)); } +}; - }; + $.fn.typeahead.Constructor = Typeahead; + /* TYPEAHEAD NO CONFLICT + * =================== */ - /* TYPEAHEAD PLUGIN DEFINITION - * =========================== */ + $.fn.typeahead.noConflict = function () { + $.fn.typeahead = old; + return this; + }; - var old = $.fn.typeahead; - $.fn.typeahead = function (option) { - var arg = arguments; - if (typeof option == 'string' && option == 'getActive') { - return this.data('active'); - } - return this.each(function () { - var $this = $(this); - var data = $this.data('typeahead'); - var options = typeof option == 'object' && option; - if (!data) $this.data('typeahead', (data = new Typeahead(this, options))); - if (typeof option == 'string' && data[option]) { - if (arg.length > 1) { - data[option].apply(data, Array.prototype.slice.call(arg, 1)); - } else { - data[option](); + /* TYPEAHEAD DATA-API + * ================== */ + + $(document).on('focus.typeahead.data-api', '[data-provide="typeahead"]', function (e) { + var $this = $(this); + if ($this.data('typeahead')) { + return; } - } + $this.typeahead($this.data()); }); - }; - - Typeahead.defaults = { - source: [], - items: 8, - menu: '', - item: '
  • ', - minLength: 1, - scrollHeight: 0, - autoSelect: true, - afterSelect: $.noop, - addItem: false, - delay: 0, - separator: 'category', - headerHtml: '', - headerDivider: '' - }; - - $.fn.typeahead.Constructor = Typeahead; - - /* TYPEAHEAD NO CONFLICT - * =================== */ - - $.fn.typeahead.noConflict = function () { - $.fn.typeahead = old; - return this; - }; - - - /* TYPEAHEAD DATA-API - * ================== */ - - $(document).on('focus.typeahead.data-api', '[data-provide="typeahead"]', function (e) { - var $this = $(this); - if ($this.data('typeahead')) return; - $this.typeahead($this.data()); - }); })); -/* jshint ignore:end */ \ No newline at end of file diff --git a/oioioi/problems/static/common/tag_selection.js b/oioioi/problems/static/common/tag_selection.js index 1ba2a2c89..928a117ba 100644 --- a/oioioi/problems/static/common/tag_selection.js +++ b/oioioi/problems/static/common/tag_selection.js @@ -59,6 +59,10 @@ function init_search_selection(id) { minLength: 2, fitToElement: true, autoSelect: false, + followLinkOnSelect: true, + itemLink: function(item) { + return item.url; + }, matcher: function(item) { // Bug fix: backspace on empty input matched last results. if(!input.val()) { diff --git a/oioioi/problems/views.py b/oioioi/problems/views.py index 3aab16ba6..603e399b3 100644 --- a/oioioi/problems/views.py +++ b/oioioi/problems/views.py @@ -93,6 +93,7 @@ show_proposal_form, ) from oioioi.programs.models import ModelSolution +from oioioi.problems.models import ProblemSite from unidecode import unidecode @@ -1130,7 +1131,6 @@ def get_algorithm_and_difficulty_tag_hints(query): return result - @uniquefy('name') def get_problem_hints(query, view_type, user): problems = _get_problems_by_query(query) @@ -1147,6 +1147,7 @@ def get_problem_hints(query, view_type, user): 'trigger': 'problem', 'name': problem.name, 'category': _("Problems"), + 'url': reverse('problem_site', kwargs={'site_key': problem.problemsite.url_key}) } for problem in problems[: getattr(settings, 'NUM_HINTS', 10)] ]