From 10a24bdc2c3870f07ec62dd710841628d325aaf6 Mon Sep 17 00:00:00 2001 From: Alexandre Flament Date: Sun, 12 Feb 2017 15:06:01 +0100 Subject: [enh] add simple theme (WIP) --- searx/static/themes/simple/js/searx.js | 1534 ++++++++++++++++++++ searx/static/themes/simple/js/searx.min.js | 4 + searx/static/themes/simple/js/searx.min.js.map | 1 + .../themes/simple/js/searx_src/00_searx_toolkit.js | 155 ++ .../themes/simple/js/searx_src/autocomplete.js | 536 +++++++ .../simple/js/searx_src/searx_imageresult.js | 151 ++ .../themes/simple/js/searx_src/searx_keyboard.js | 360 +++++ .../themes/simple/js/searx_src/searx_mapresult.js | 175 +++ .../themes/simple/js/searx_src/searx_results.js | 63 + .../themes/simple/js/searx_src/searx_search.js | 94 ++ 10 files changed, 3073 insertions(+) create mode 100644 searx/static/themes/simple/js/searx.js create mode 100644 searx/static/themes/simple/js/searx.min.js create mode 100644 searx/static/themes/simple/js/searx.min.js.map create mode 100644 searx/static/themes/simple/js/searx_src/00_searx_toolkit.js create mode 100644 searx/static/themes/simple/js/searx_src/autocomplete.js create mode 100644 searx/static/themes/simple/js/searx_src/searx_imageresult.js create mode 100644 searx/static/themes/simple/js/searx_src/searx_keyboard.js create mode 100644 searx/static/themes/simple/js/searx_src/searx_mapresult.js create mode 100644 searx/static/themes/simple/js/searx_src/searx_results.js create mode 100644 searx/static/themes/simple/js/searx_src/searx_search.js (limited to 'searx/static/themes/simple/js') diff --git a/searx/static/themes/simple/js/searx.js b/searx/static/themes/simple/js/searx.js new file mode 100644 index 000000000..64329fde1 --- /dev/null +++ b/searx/static/themes/simple/js/searx.js @@ -0,0 +1,1534 @@ +/** +* searx is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* searx is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with searx. If not, see < http://www.gnu.org/licenses/ >. +* +* (C) 2017 by Alexandre Flament, +* +*/ +(function(w, d, searx) { + + 'use strict'; + + // not invented here tookit with bugs fixed elsewhere + // purposes : be just good enough and as small as possible + + // from https://plainjs.com/javascript/events/live-binding-event-handlers-14/ + if (w.Element) { + (function(ElementPrototype) { + ElementPrototype.matches = ElementPrototype.matches || + ElementPrototype.matchesSelector || + ElementPrototype.webkitMatchesSelector || + ElementPrototype.msMatchesSelector || + function(selector) { + var node = this, nodes = (node.parentNode || node.document).querySelectorAll(selector), i = -1; + while (nodes[++i] && nodes[i] != node); + return !!nodes[i]; + }; + })(Element.prototype); + } + + function callbackSafe(callback, el, e) { + try { + callback.call(el, e); + } catch (exception) { + console.log(exception); + } + } + + searx = searx || {}; + + searx.on = function(obj, eventType, callback, useCapture) { + useCapture = useCapture || false; + if (typeof obj !== 'string') { + // obj HTMLElement, HTMLDocument + obj.addEventListener(eventType, callback, useCapture); + } else { + // obj is a selector + d.addEventListener(eventType, function(e) { + var el = e.target || e.srcElement, found = false; + while (el && el.matches && el !== d && !(found = el.matches(obj))) el = el.parentElement; + if (found) callbackSafe(callback, el, e); + }, useCapture); + } + }; + + searx.ready = function(callback) { + if (document.readyState != 'loading') { + callback.call(w); + } else { + w.addEventListener('DOMContentLoaded', callback.bind(w)); + } + }; + + searx.http = function(method, url, callback) { + var req = new XMLHttpRequest(), + resolve = function() {}, + reject = function() {}, + promise = { + then: function(callback) { resolve = callback; return promise; }, + catch: function(callback) { reject = callback; return promise; } + }; + + try { + req.open(method, url, true); + + // On load + req.onload = function() { + if (req.status == 200) { + resolve(req.response, req.responseType); + } else { + reject(Error(req.statusText)); + } + }; + + // Handle network errors + req.onerror = function() { + reject(Error("Network Error")); + }; + + req.onabort = function() { + reject(Error("Transaction is aborted")); + }; + + // Make the request + req.send(); + } catch (ex) { + reject(ex); + } + + return promise; + }; + + searx.loadStyle = function(src) { + var path = searx.staticPath + src, + id = "style_" + src.replace('.', '_'), + s = d.getElementById(id); + if (s === null) { + s = d.createElement('link'); + s.setAttribute('id', id); + s.setAttribute('rel', 'stylesheet'); + s.setAttribute('type', 'text/css'); + s.setAttribute('href', path); + d.body.appendChild(s); + } + }; + + searx.loadScript = function(src, callback) { + var path = searx.staticPath + src, + id = "script_" + src.replace('.', '_'), + s = d.getElementById(id); + if (s === null) { + s = d.createElement('script'); + s.setAttribute('id', id); + s.setAttribute('src', path); + s.onload = callback; + s.onerror = function() { + s.setAttribute('error', '1'); + }; + d.body.appendChild(s); + } else if (!s.hasAttribute('error')) { + try { + callback.apply(s, []); + } catch (exception) { + console.log(exception); + } + } else { + console.log("callback not executed : script '" + path + "' not loaded."); + } + }; + + searx.on('.close', 'click', function(e) { + var el = e.target || e.srcElement; + this.parentNode.style.display="None"; + }); + return searx; +})(window, document, window.searx); +;(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.AutoComplete = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o @baptistedonaux + */ +var AutoComplete = (function () { + // Constructor + function AutoComplete(params, selector) { + if (params === void 0) { params = {}; } + if (selector === void 0) { selector = "[data-autocomplete]"; } + if (Array.isArray(selector)) { + selector.forEach(function (s) { + new AutoComplete(params, s); + }); + } + else if (typeof selector == "string") { + var elements = document.querySelectorAll(selector); + Array.prototype.forEach.call(elements, function (input) { + new AutoComplete(params, input); + }); + } + else { + var specificParams = AutoComplete.merge(AutoComplete.defaults, params, { + DOMResults: document.createElement("div") + }); + AutoComplete.prototype.create(specificParams, selector); + return specificParams; + } + } + AutoComplete.prototype.create = function (params, element) { + params.Input = element; + if (params.Input.nodeName.match(/^INPUT$/i) && (params.Input.hasAttribute("type") === false || params.Input.getAttribute("type").match(/^TEXT|SEARCH$/i))) { + params.Input.setAttribute("autocomplete", "off"); + params._Position(params); + params.Input.parentNode.appendChild(params.DOMResults); + params.$Listeners = { + blur: params._Blur.bind(params), + destroy: AutoComplete.prototype.destroy.bind(null, params), + focus: params._Focus.bind(params), + keyup: AutoComplete.prototype.event.bind(null, params, EventType.KEYUP), + keydown: AutoComplete.prototype.event.bind(null, params, EventType.KEYDOWN), + position: params._Position.bind(params) + }; + for (var event in params.$Listeners) { + params.Input.addEventListener(event, params.$Listeners[event]); + } + } + }; + AutoComplete.prototype.getEventsByType = function (params, type) { + var mappings = {}; + for (var key in params.KeyboardMappings) { + var event = EventType.KEYUP; + if (params.KeyboardMappings[key].Event !== undefined) { + event = params.KeyboardMappings[key].Event; + } + if (event == type) { + mappings[key] = params.KeyboardMappings[key]; + } + } + return mappings; + }; + AutoComplete.prototype.event = function (params, type, event) { + var eventIdentifier = function (condition) { + if ((match === true && mapping.Operator == ConditionOperator.AND) || (match === false && mapping.Operator == ConditionOperator.OR)) { + condition = AutoComplete.merge({ + Not: false + }, condition); + if (condition.hasOwnProperty("Is")) { + if (condition.Is == event.keyCode) { + match = !condition.Not; + } + else { + match = condition.Not; + } + } + else if (condition.hasOwnProperty("From") && condition.hasOwnProperty("To")) { + if (event.keyCode >= condition.From && event.keyCode <= condition.To) { + match = !condition.Not; + } + else { + match = condition.Not; + } + } + } + }; + for (var name in AutoComplete.prototype.getEventsByType(params, type)) { + var mapping = AutoComplete.merge({ + Operator: ConditionOperator.AND + }, params.KeyboardMappings[name]), match = ConditionOperator.AND == mapping.Operator; + mapping.Conditions.forEach(eventIdentifier); + if (match === true) { + mapping.Callback.call(params, event); + } + } + }; + AutoComplete.prototype.makeRequest = function (params, callback) { + var propertyHttpHeaders = Object.getOwnPropertyNames(params.HttpHeaders), request = new XMLHttpRequest(), method = params._HttpMethod(), url = params._Url(), queryParams = params._Pre(), queryParamsStringify = encodeURIComponent(params._QueryArg()) + "=" + encodeURIComponent(queryParams); + if (method.match(/^GET$/i)) { + if (url.indexOf("?") !== -1) { + url += "&" + queryParamsStringify; + } + else { + url += "?" + queryParamsStringify; + } + } + request.open(method, url, true); + for (var i = propertyHttpHeaders.length - 1; i >= 0; i--) { + request.setRequestHeader(propertyHttpHeaders[i], params.HttpHeaders[propertyHttpHeaders[i]]); + } + request.onreadystatechange = function () { + if (request.readyState == 4 && request.status == 200) { + params.$Cache[queryParams] = request.response; + callback(request.response); + } + }; + return request; + }; + AutoComplete.prototype.ajax = function (params, request, timeout) { + if (timeout === void 0) { timeout = true; } + if (params.$AjaxTimer) { + window.clearTimeout(params.$AjaxTimer); + } + if (timeout === true) { + params.$AjaxTimer = window.setTimeout(AutoComplete.prototype.ajax.bind(null, params, request, false), params.Delay); + } + else { + if (params.Request) { + params.Request.abort(); + } + params.Request = request; + params.Request.send(params._QueryArg() + "=" + params._Pre()); + } + }; + AutoComplete.prototype.cache = function (params, callback) { + var response = params._Cache(params._Pre()); + if (response === undefined) { + var request = AutoComplete.prototype.makeRequest(params, callback); + AutoComplete.prototype.ajax(params, request); + } + else { + callback(response); + } + }; + AutoComplete.prototype.destroy = function (params) { + for (var event in params.$Listeners) { + params.Input.removeEventListener(event, params.$Listeners[event]); + } + params.DOMResults.parentNode.removeChild(params.DOMResults); + }; + return AutoComplete; +}()); +AutoComplete.merge = function () { + var merge = {}, tmp; + for (var i = 0; i < arguments.length; i++) { + for (tmp in arguments[i]) { + merge[tmp] = arguments[i][tmp]; + } + } + return merge; +}; +AutoComplete.defaults = { + Delay: 150, + EmptyMessage: "No result here", + Highlight: { + getRegex: function (value) { + return new RegExp(value, "ig"); + }, + transform: function (value) { + return "" + value + ""; + } + }, + HttpHeaders: { + "Content-type": "application/x-www-form-urlencoded" + }, + Limit: 0, + MinChars: 0, + HttpMethod: "GET", + QueryArg: "q", + Url: null, + KeyboardMappings: { + "Enter": { + Conditions: [{ + Is: 13, + Not: false + }], + Callback: function (event) { + if (this.DOMResults.getAttribute("class").indexOf("open") != -1) { + var liActive = this.DOMResults.querySelector("li.active"); + if (liActive !== null) { + event.preventDefault(); + this._Select(liActive); + this.DOMResults.setAttribute("class", "autocomplete"); + } + } + }, + Operator: ConditionOperator.AND, + Event: EventType.KEYDOWN + }, + "KeyUpAndDown_down": { + Conditions: [{ + Is: 38, + Not: false + }, + { + Is: 40, + Not: false + }], + Callback: function (event) { + event.preventDefault(); + }, + Operator: ConditionOperator.OR, + Event: EventType.KEYDOWN + }, + "KeyUpAndDown_up": { + Conditions: [{ + Is: 38, + Not: false + }, + { + Is: 40, + Not: false + }], + Callback: function (event) { + event.preventDefault(); + var first = this.DOMResults.querySelector("li:first-child:not(.locked)"), last = this.DOMResults.querySelector("li:last-child:not(.locked)"), active = this.DOMResults.querySelector("li.active"); + if (active) { + var currentIndex = Array.prototype.indexOf.call(active.parentNode.children, active), position = currentIndex + (event.keyCode - 39), lisCount = this.DOMResults.getElementsByTagName("li").length; + if (position < 0) { + position = lisCount - 1; + } + else if (position >= lisCount) { + position = 0; + } + active.classList.remove("active"); + active.parentElement.children.item(position).classList.add("active"); + } + else if (last && event.keyCode == 38) { + last.classList.add("active"); + } + else if (first) { + first.classList.add("active"); + } + }, + Operator: ConditionOperator.OR, + Event: EventType.KEYUP + }, + "AlphaNum": { + Conditions: [{ + Is: 13, + Not: true + }, { + From: 35, + To: 40, + Not: true + }], + Callback: function () { + var oldValue = this.Input.getAttribute("data-autocomplete-old-value"), currentValue = this._Pre(); + if (currentValue !== "" && currentValue.length >= this._MinChars()) { + if (!oldValue || currentValue != oldValue) { + this.DOMResults.setAttribute("class", "autocomplete open"); + } + AutoComplete.prototype.cache(this, function (response) { + this._Render(this._Post(response)); + this._Open(); + }.bind(this)); + } + }, + Operator: ConditionOperator.AND, + Event: EventType.KEYUP + } + }, + DOMResults: null, + Request: null, + Input: null, + /** + * Return the message when no result returns + */ + _EmptyMessage: function () { + var emptyMessage = ""; + if (this.Input.hasAttribute("data-autocomplete-empty-message")) { + emptyMessage = this.Input.getAttribute("data-autocomplete-empty-message"); + } + else if (this.EmptyMessage !== false) { + emptyMessage = this.EmptyMessage; + } + else { + emptyMessage = ""; + } + return emptyMessage; + }, + /** + * Returns the maximum number of results + */ + _Limit: function () { + var limit = this.Input.getAttribute("data-autocomplete-limit"); + if (isNaN(limit) || limit === null) { + return this.Limit; + } + return parseInt(limit, 10); + }, + /** + * Returns the minimum number of characters entered before firing ajax + */ + _MinChars: function () { + var minchars = this.Input.getAttribute("data-autocomplete-minchars"); + if (isNaN(minchars) || minchars === null) { + return this.MinChars; + } + return parseInt(minchars, 10); + }, + /** + * Apply transformation on labels response + */ + _Highlight: function (label) { + return label.replace(this.Highlight.getRegex(this._Pre()), this.Highlight.transform); + }, + /** + * Returns the HHTP method to use + */ + _HttpMethod: function () { + if (this.Input.hasAttribute("data-autocomplete-method")) { + return this.Input.getAttribute("data-autocomplete-method"); + } + return this.HttpMethod; + }, + /** + * Returns the query param to use + */ + _QueryArg: function () { + if (this.Input.hasAttribute("data-autocomplete-param-name")) { + return this.Input.getAttribute("data-autocomplete-param-name"); + } + return this.QueryArg; + }, + /** + * Returns the URL to use for AJAX request + */ + _Url: function () { + if (this.Input.hasAttribute("data-autocomplete")) { + return this.Input.getAttribute("data-autocomplete"); + } + return this.Url; + }, + /** + * Manage the close + */ + _Blur: function (now) { + if (now === true) { + this.DOMResults.setAttribute("class", "autocomplete"); + this.Input.setAttribute("data-autocomplete-old-value", this.Input.value); + } + else { + var params = this; + setTimeout(function () { + params._Blur(true); + }, 150); + } + }, + /** + * Manage the cache + */ + _Cache: function (value) { + return this.$Cache[value]; + }, + /** + * Manage the open + */ + _Focus: function () { + var oldValue = this.Input.getAttribute("data-autocomplete-old-value"); + if ((!oldValue || this.Input.value != oldValue) && this._MinChars() <= this.Input.value.length) { + this.DOMResults.setAttribute("class", "autocomplete open"); + } + }, + /** + * Bind all results item if one result is opened + */ + _Open: function () { + var params = this; + Array.prototype.forEach.call(this.DOMResults.getElementsByTagName("li"), function (li) { + if (li.getAttribute("class") != "locked") { + li.onclick = function (event) { + params._Select(li); + }; + li.onmouseenter = function () { + var active = params.DOMResults.querySelector("li.active"); + if (active !== li) { + if (active !== null) { + active.classList.remove("active"); + } + li.classList.add("active"); + } + }; + } + }); + }, + /** + * Position the results HTML element + */ + _Position: function () { + this.DOMResults.setAttribute("class", "autocomplete"); + this.DOMResults.setAttribute("style", "top:" + (this.Input.offsetTop + this.Input.offsetHeight) + "px;left:" + this.Input.offsetLeft + "px;width:" + this.Input.clientWidth + "px;"); + }, + /** + * Execute the render of results DOM element + */ + _Render: function (response) { + var ul; + if (typeof response == "string") { + ul = this._RenderRaw(response); + } + else { + ul = this._RenderResponseItems(response); + } + if (this.DOMResults.hasChildNodes()) { + this.DOMResults.removeChild(this.DOMResults.childNodes[0]); + } + this.DOMResults.appendChild(ul); + }, + /** + * ResponseItems[] rendering + */ + _RenderResponseItems: function (response) { + var ul = document.createElement("ul"), li = document.createElement("li"), limit = this._Limit(); + // Order + if (limit < 0) { + response = response.reverse(); + } + else if (limit === 0) { + limit = response.length; + } + for (var item = 0; item < Math.min(Math.abs(limit), response.length); item++) { + li.innerHTML = response[item].Label; + li.setAttribute("data-autocomplete-value", response[item].Value); + ul.appendChild(li); + li = document.createElement("li"); + } + return ul; + }, + /** + * string response rendering (RAW HTML) + */ + _RenderRaw: function (response) { + var ul = document.createElement("ul"), li = document.createElement("li"); + if (response.length > 0) { + this.DOMResults.innerHTML = response; + } + else { + var emptyMessage = this._EmptyMessage(); + if (emptyMessage !== "") { + li.innerHTML = emptyMessage; + li.setAttribute("class", "locked"); + ul.appendChild(li); + } + } + return ul; + }, + /** + * Deal with request response + */ + _Post: function (response) { + try { + var returnResponse = []; + //JSON return + var json = JSON.parse(response); + if (Object.keys(json).length === 0) { + return ""; + } + if (Array.isArray(json)) { + for (var i = 0; i < Object.keys(json).length; i++) { + returnResponse[returnResponse.length] = { "Value": json[i], "Label": this._Highlight(json[i]) }; + } + } + else { + for (var value in json) { + returnResponse.push({ + "Value": value, + "Label": this._Highlight(json[value]) + }); + } + } + return returnResponse; + } + catch (event) { + //HTML return + return response; + } + }, + /** + * Return the autocomplete value to send (before request) + */ + _Pre: function () { + return this.Input.value; + }, + /** + * Choice one result item + */ + _Select: function (item) { + console.log('test test test'); + if (item.hasAttribute("data-autocomplete-value")) { + this.Input.value = item.getAttribute("data-autocomplete-value"); + } + else { + this.Input.value = item.innerHTML; + } + this.Input.setAttribute("data-autocomplete-old-value", this.Input.value); + }, + $AjaxTimer: null, + $Cache: {}, + $Listeners: {} +}; +module.exports = AutoComplete; + +},{}]},{},[1])(1) +}); +;/** +* +* Google Image Layout v0.0.1 +* Description, by Anh Trinh. +* Heavily modified for searx +* http://trinhtrunganh.com +* +* @license Free to use under the MIT License. +* +*/ +(function(w, d) { + 'use strict'; + + function ImageLayout(container_selector, results_selector, img_selector, maxHeight) { + this.container_selector = container_selector; + this.results_selector = results_selector; + this.img_selector = img_selector; + this.margin = 10; + this.maxHeight = maxHeight; + this._alignAllDone = true; + } + + /** + * Get the height that make all images fit the container + * + * width = w1 + w2 + w3 + ... = r1*h + r2*h + r3*h + ... + * + * @param {[type]} images the images to be calculated + * @param {[type]} width the container witdth + * @param {[type]} margin the margin between each image + * + * @return {[type]} the height + */ + ImageLayout.prototype._getHeigth = function(images, width) { + var r = 0, + img; + + width -= images.length * this.margin; + for (var i = 0; i < images.length; i++) { + img = images[i]; + if ((img.naturalWidth > 0) && (img.naturalHeight > 0)) { + r += img.naturalWidth / img.naturalHeight; + } else { + // assume that not loaded images are square + r += 1; + } + } + + return width / r; //have to round down because Firefox will automatically roundup value with number of decimals > 3 + }; + + ImageLayout.prototype._setSize = function(images, height) { + var img, imgWidth, imagesLength = images.length; + for (var i = 0; i < imagesLength; i++) { + img = images[i]; + if ((img.naturalWidth > 0) && (img.naturalHeight > 0)) { + imgWidth = height * img.naturalWidth / img.naturalHeight; + } else { + // not loaded image : make it square as _getHeigth said it + imgWidth = height; + } + img.style.width = imgWidth + 'px'; + img.style.height = height + 'px'; + img.style.marginLeft = '3px'; + img.style.marginTop = '3px'; + img.style.marginRight = this.margin - 7 + 'px'; // -4 is the negative margin of the inline element + img.style.marginBottom = this.margin - 7 + 'px'; + } + }; + + ImageLayout.prototype._alignImgs = function(imgGroup) { + var slice, h, + containerWidth = d.querySelector(this.container_selector).clientWidth; + + w: while (imgGroup.length > 0) { + for (var i = 1; i <= imgGroup.length; i++) { + slice = imgGroup.slice(0, i); + h = this._getHeigth(slice, containerWidth); + if (h < this.maxHeight) { + this._setSize(slice, h); + imgGroup = imgGroup.slice(i); + continue w; + } + } + this._setSize(slice, Math.min(this.maxHeight, h)); + break; + } + }; + + ImageLayout.prototype.align = function(results_selector) { + var results_selectorNode = d.querySelectorAll(this.results_selector), + results_length = results_selectorNode.length, + previous = null, + current = null, + imgGroup = []; + for (var i = 0; i < results_length; i++) { + current = results_selectorNode[i]; + if (current.previousElementSibling !== previous && imgGroup.length > 0) { + // the current image is not conected to previous one + // so the current image is the start of a new group of images. + // so call _alignImgs to align the current group + this._alignImgs(imgGroup); + // and start a new empty group of images + imgGroup = []; + } + // add the current image to the group (only the img tag) + imgGroup.push(current.querySelector(this.img_selector)); + // update the previous variable + previous = current; + } + // align the remaining images + if (imgGroup.length > 0) { + this._alignImgs(imgGroup); + } + }; + + ImageLayout.prototype.watch = function() { + var i, img, imgGroup, imgNodeLength, + obj = this, + results_nodes = d.querySelectorAll(this.results_selector), + results_length = results_nodes.length; + + function align(e) { + obj.align(); + } + + function throttleAlign(e) { + if (obj._alignAllDone) { + obj._alignAllDone = false; + setTimeout(function() { + obj.align(); + obj._alignAllDone = true; + }, 100); + } + } + + w.addEventListener('resize', throttleAlign); + w.addEventListener('pageshow', align); + + for (i = 0; i < results_length; i++) { + img = results_nodes[i].querySelector(this.img_selector); + if (typeof img !== 'undefined') { + img.addEventListener('load', throttleAlign); + img.addEventListener('error', throttleAlign); + } + } + }; + + w.searx.ImageLayout = ImageLayout; + +})(window, document); +;searx.ready(function() { + + searx.on('.result', 'click', function() { + highlightResult(this)(true); + }); + + searx.on('.result a', 'focus', function(e) { + var el = e.target; + while (el !== undefined) { + if (el.classList.contains('result')) { + if (el.getAttribute("data-vim-selected") === null) { + highlightResult(el)(true); + } + break; + } + el = el.parentNode; + } + }, true); + + var vimKeys = { + 27: { + key: 'Escape', + fun: removeFocus, + des: 'remove focus from the focused input', + cat: 'Control' + }, + 73: { + key: 'i', + fun: searchInputFocus, + des: 'focus on the search input', + cat: 'Control' + }, + 66: { + key: 'b', + fun: scrollPage(-window.innerHeight), + des: 'scroll one page up', + cat: 'Navigation' + }, + 70: { + key: 'f', + fun: scrollPage(window.innerHeight), + des: 'scroll one page down', + cat: 'Navigation' + }, + 85: { + key: 'u', + fun: scrollPage(-window.innerHeight / 2), + des: 'scroll half a page up', + cat: 'Navigation' + }, + 68: { + key: 'd', + fun: scrollPage(window.innerHeight / 2), + des: 'scroll half a page down', + cat: 'Navigation' + }, + 71: { + key: 'g', + fun: scrollPageTo(-document.body.scrollHeight, 'top'), + des: 'scroll to the top of the page', + cat: 'Navigation' + }, + 86: { + key: 'v', + fun: scrollPageTo(document.body.scrollHeight, 'bottom'), + des: 'scroll to the bottom of the page', + cat: 'Navigation' + }, + 75: { + key: 'k', + fun: highlightResult('up'), + des: 'select previous search result', + cat: 'Results' + }, + 74: { + key: 'j', + fun: highlightResult('down'), + des: 'select next search result', + cat: 'Results' + }, + 80: { + key: 'p', + fun: pageButtonClick(0), + des: 'go to previous page', + cat: 'Results' + }, + 78: { + key: 'n', + fun: pageButtonClick(1), + des: 'go to next page', + cat: 'Results' + }, + 79: { + key: 'o', + fun: openResult(false), + des: 'open search result', + cat: 'Results' + }, + 84: { + key: 't', + fun: openResult(true), + des: 'open the result in a new tab', + cat: 'Results' + }, + 82: { + key: 'r', + fun: reloadPage, + des: 'reload page from the server', + cat: 'Control' + }, + 72: { + key: 'h', + fun: toggleHelp, + des: 'toggle help window', + cat: 'Other' + } + }; + + searx.on(document, "keyup", function(e) { + // check for modifiers so we don't break browser's hotkeys + if (vimKeys.hasOwnProperty(e.keyCode) && !e.ctrlKey && !e.altKey && !e.shiftKey && !e.metaKey) { + var tagName = e.target.tagName.toLowerCase(); + if (e.keyCode === 27) { + if (tagName === 'input' || tagName === 'select' || tagName === 'textarea') { + vimKeys[e.keyCode].fun(); + } + } else { + if (e.target === document.body || tagName === 'a' || tagName === 'button') { + vimKeys[e.keyCode].fun(); + } + } + } + }); + + function highlightResult(which) { + return function(noScroll) { + var current = document.querySelector('.result[data-vim-selected]'), + effectiveWhich = which; + if (current === null) { + // no selection : choose the first one + current = document.querySelector('.result'); + if (current === null) { + // no first one : there are no results + return; + } + // replace up/down actions by selecting first one + if (which === "down" || which === "up") { + effectiveWhich = current; + } + } + + var next, results = document.querySelectorAll('.result'); + + if (typeof effectiveWhich !== 'string') { + next = effectiveWhich; + } else { + switch (effectiveWhich) { + case 'visible': + var top = document.documentElement.scrollTop || document.body.scrollTop; + var bot = top + document.documentElement.clientHeight; + + for (var i = 0; i < results.length; i++) { + next = results[i]; + var etop = next.offsetTop; + var ebot = etop + next.clientHeight; + + if ((ebot <= bot) && (etop > top)) { + break; + } + } + break; + case 'down': + next = current.nextElementSibling; + if (next === null) { + next = results[0]; + } + break; + case 'up': + next = current.previousElementSibling; + if (next === null) { + next = results[results.length - 1]; + } + break; + case 'bottom': + next = results[results.length - 1]; + break; + case 'top': + /* falls through */ + default: + next = results[0]; + } + } + + if (next) { + current.removeAttribute('data-vim-selected'); + next.setAttribute('data-vim-selected', 'true'); + var link = next.querySelector('h3 a') || next.querySelector('a'); + if (link !== null) { + link.focus(); + } + if (!noScroll) { + scrollPageToSelected(); + } + } + }; + } + + function reloadPage() { + document.location.reload(true); + } + + function removeFocus() { + if (document.activeElement) { + document.activeElement.blur(); + } + } + + function pageButtonClick(num) { + return function() { + var buttons = $('div#pagination button[type="submit"]'); + if (buttons.length !== 2) { + console.log('page navigation with this theme is not supported'); + return; + } + if (num >= 0 && num < buttons.length) { + buttons[num].click(); + } else { + console.log('pageButtonClick(): invalid argument'); + } + }; + } + + function scrollPageToSelected() { + var sel = document.querySelector('.result[data-vim-selected]'); + if (sel === null) { + return; + } + var wtop = document.documentElement.scrollTop || document.body.scrollTop, + wheight = document.documentElement.clientHeight, + etop = sel.offsetTop, + ebot = etop + sel.clientHeight, + offset = 120; + // first element ? + if ((sel.previousElementSibling === null) && (ebot < wheight)) { + // set to the top of page if the first element + // is fully included in the viewport + window.scroll(window.scrollX, 0); + return; + } + if (wtop > (etop - offset)) { + window.scroll(window.scrollX, etop - offset); + } else { + var wbot = wtop + wheight; + if (wbot < (ebot + offset)) { + window.scroll(window.scrollX, ebot - wheight + offset); + } + } + } + + function scrollPage(amount) { + return function() { + window.scrollBy(0, amount); + highlightResult('visible')(); + }; + } + + function scrollPageTo(position, nav) { + return function() { + window.scrollTo(0, position); + highlightResult(nav)(); + }; + } + + function searchInputFocus() { + window.scrollTo(0, 0); + document.querySelector('#q').focus(); + } + + function openResult(newTab) { + return function() { + var link = document.querySelector('.result[data-vim-selected] h3 a'); + if (link !== null) { + var url = link.getAttribute('href'); + if (newTab) { + window.open(url); + } else { + window.location.href = url; + } + } + }; + } + + function toggleHelp() { + var helpPanel = document.querySelector('#vim-hotkeys-help'); + if (helpPanel.length) { + helpPanel.classList.toggle('hidden'); + return; + } + + var categories = {}; + + for (var k in vimKeys) { + var key = vimKeys[k]; + categories[key.cat] = categories[key.cat] || []; + categories[key.cat].push(key); + } + + var sorted = Object.keys(categories).sort(function(a, b) { + return categories[b].length - categories[a].length; + }); + + if (sorted.length === 0) { + return; + } + + var html = '
'; + html += '
'; + + html += '
'; + html += '
'; + html += '

How to navigate searx with Vim-like hotkeys

'; + html += '
'; // col-sm-12 + html += '
'; // row + + for (var i = 0; i < sorted.length; i++) { + var cat = categories[sorted[i]]; + + var lastCategory = i === (sorted.length - 1); + var first = i % 2 === 0; + + if (first) { + html += '
'; + } + html += '
'; + + html += '
'; + html += '
' + cat[0].cat + '
'; + html += '
'; + html += '
    '; + + for (var cj in cat) { + html += '
  • ' + cat[cj].key + ' ' + cat[cj].des + '
  • '; + } + + html += '
'; + html += '
'; // panel-body + html += '
'; // panel + html += '
'; // col-sm-* + + if (!first || lastCategory) { + html += '
'; // row + } + } + + html += '
'; // container-fluid + html += '
'; // vim-hotkeys-help + + $('body').append(html); + } +}); +;/** +* searx is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* searx is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with searx. If not, see < http://www.gnu.org/licenses/ >. +* +* (C) 2014 by Thomas Pointhuber, +* (C) 2017 by Alexandre Flament, +*/ +(function (w, d, searx) { + 'use strict'; + + searx.ready(function () { + searx.on('.searx_overpass_request', 'click', function(event) { + // no more request + this.classList.remove("searx_overpass_request"); + + // + var overpass_url = "https://overpass-api.de/api/interpreter?data="; + var query_start = overpass_url + "[out:json][timeout:25];("; + var query_end = ");out meta;"; + + var osm_id = this.dataset.osmId; + var osm_type = this.dataset.osmType; + var result_table = d.querySelector("#" + this.dataset.resultTable); + var result_table_loadicon = d.querySelector("#" + this.dataset.resultTableLoadicon); + + // tags which can be ignored + var osm_ignore_tags = [ "addr:city", "addr:country", "addr:housenumber", "addr:postcode", "addr:street" ]; + + if(osm_id && osm_type && result_table) { + var query = null; + switch(osm_type) { + case 'node': + query = query_start + "node(" + osm_id + ");" + query_end; + break; + case 'way': + query = query_start + "way(" + osm_id + ");" + query_end; + break; + case 'relation': + query = query_start + "relation(" + osm_id + ");" + query_end; + break; + default: + break; + } + if(query) { + // console.log(query); + searx.http( 'GET', query ).then(function(html, contentType) { + html = JSON.parse(html); + if(html && html.elements && html.elements[0]) { + var element = html.elements[0]; + var newHtml = ""; + for (var row in element.tags) { + if(element.tags.name === null || osm_ignore_tags.indexOf(row) == -1) { + newHtml += "" + row + ""; + switch(row) { + case "phone": + case "fax": + newHtml += "" + element.tags[row] + ""; + break; + case "email": + newHtml += "" + element.tags[row] + ""; + break; + case "website": + case "url": + newHtml += "" + element.tags[row] + ""; + break; + case "wikidata": + newHtml += "" + element.tags[row] + ""; + break; + case "wikipedia": + if(element.tags[row].indexOf(":") != -1) { + newHtml += "" + element.tags[row] + ""; + break; + } + /* jshint ignore:start */ + default: + /* jshint ignore:end */ + newHtml += element.tags[row]; + break; + } + newHtml += ""; + } + } + result_table_loadicon.classList.add('invisible'); + result_table.classList.remove('invisible'); + result_table.querySelector("tbody").innerHTML = newHtml; + } + }) + .catch(function() { + result_table_loadicon.innerHTML = result_table_loadicon.innerHTML + "

could not load data!

"; + }); + } + } + + // this event occour only once per element + event.preventDefault(); + }); + + searx.on('.searx_init_map', 'click', function(event) { + // no more request + this.classList.remove("searx_init_map"); + + // + var leaflet_target = this.dataset.leafletTarget; + var map_lon = parseFloat(this.dataset.mapLon); + var map_lat = parseFloat(this.dataset.mapLat); + var map_zoom = parseFloat(this.dataset.mapZoom); + var map_boundingbox = JSON.parse(this.dataset.mapBoundingbox); + var map_geojson = JSON.parse(this.dataset.mapGeojson); + + searx.loadStyle('leaflet/leaflet.css'); + searx.loadScript('leaflet/leaflet.js', function() { + var map_bounds = null; + if(map_boundingbox) { + var southWest = L.latLng(map_boundingbox[0], map_boundingbox[2]); + var northEast = L.latLng(map_boundingbox[1], map_boundingbox[3]); + map_bounds = L.latLngBounds(southWest, northEast); + } + + // init map + var map = L.map(leaflet_target); + // create the tile layer with correct attribution + var osmMapnikUrl='https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'; + var osmMapnikAttrib='Map data © OpenStreetMap contributors'; + var osmMapnik = new L.TileLayer(osmMapnikUrl, {minZoom: 1, maxZoom: 19, attribution: osmMapnikAttrib}); + var osmWikimediaUrl='https://maps.wikimedia.org/osm-intl/{z}/{x}/{y}.png'; + var osmWikimediaAttrib = 'Wikimedia maps beta | Maps data © OpenStreetMap contributors'; + var osmWikimedia = new L.TileLayer(osmWikimediaUrl, {minZoom: 1, maxZoom: 19, attribution: osmWikimediaAttrib}); + // init map view + if(map_bounds) { + // TODO hack: https://github.com/Leaflet/Leaflet/issues/2021 + // Still useful ? + setTimeout(function () { + map.fitBounds(map_bounds, { + maxZoom:17 + }); + }, 0); + } else if (map_lon && map_lat) { + if(map_zoom) { + map.setView(new L.latLng(map_lat, map_lon),map_zoom); + } else { + map.setView(new L.latLng(map_lat, map_lon),8); + } + } + + map.addLayer(osmMapnik); + + var baseLayers = { + "OSM Mapnik": osmMapnik/*, + "OSM Wikimedia": osmWikimedia*/ + }; + + L.control.layers(baseLayers).addTo(map); + + if(map_geojson) { + L.geoJson(map_geojson).addTo(map); + } /*else if(map_bounds) { + L.rectangle(map_bounds, {color: "#ff7800", weight: 3, fill:false}).addTo(map); + }*/ + }); + + // this event occour only once per element + event.preventDefault(); + }); + }); +})(window, document, window.searx); +;/** +* searx is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* searx is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with searx. If not, see < http://www.gnu.org/licenses/ >. +* +* (C) 2017 by Alexandre Flament, +*/ +(function(w, d, searx) { + 'use strict'; + + searx.ready(function() { + searx.image_thumbnail_layout = new searx.ImageLayout('#urls', '#urls .result-images', 'img.image_thumbnail', 200); + searx.image_thumbnail_layout.watch(); + + searx.on('.btn-collapse', 'click', function(event) { + var btnLabelCollapsed = this.getAttribute('data-btn-text-collapsed'); + var btnLabelNotCollapsed = this.getAttribute('data-btn-text-not-collapsed'); + var target = this.getAttribute('data-target'); + var targetElement = d.querySelector(target); + var html = this.innerHTML; + if (this.classList.contains('collapsed')) { + html = html.replace(btnLabelCollapsed, btnLabelNotCollapsed); + } else { + html = html.replace(btnLabelNotCollapsed, btnLabelCollapsed); + } + this.innerHTML = html; + this.classList.toggle('collapsed'); + targetElement.classList.toggle('invisible'); + }); + + searx.on('.media-loader', 'click', function(event) { + var target = this.getAttribute('data-target'); + var iframe_load = d.querySelector(target + ' > iframe'); + var srctest = iframe_load.getAttribute('src'); + if (srctest === null || srctest === undefined || srctest === false) { + iframe_load.setAttribute('src', iframe_load.getAttribute('data-src')); + } + }); + + w.addEventListener('scroll', function() { + var e = d.getElementById('backToTop'), + scrollTop = document.documentElement.scrollTop || document.body.scrollTop; + if (e !== null) { + if (scrollTop >= 200) { + e.style.opacity = 1; + } else { + e.style.opacity = 0; + } + } + }); + + }); + +})(window, document, window.searx); +;/** +* searx is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* searx is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with searx. If not, see < http://www.gnu.org/licenses/ >. +* +* (C) 2017 by Alexandre Flament, +*/ +(function(w, d, searx) { + 'use strict'; + + var firstFocus = true, qinput_id = "q", qinput; + + function placeCursorAtEnd(element) { + if (element.setSelectionRange) { + var len = element.value.length; + element.setSelectionRange(len, len); + } + } + + function submitIfQuery() { + if (qinput.value.length > 0) { + var search = document.getElementById('search'); + setTimeout(search.submit.bind(search), 0); + } + } + + searx.ready(function() { + qinput = d.getElementById(qinput_id); + + function placeCursorAtEndOnce(e) { + if (firstFocus) { + placeCursorAtEnd(qinput); + firstFocus = false; + } else { + // e.preventDefault(); + } + } + + if (qinput !== null) { + // autocompleter + if (searx.autocompleter) { + searx.autocomplete = AutoComplete.call(w, { + Url: "./autocompleter", + EmptyMessage: searx.noItemFound, + HttpMethod: searx.method, + MinChars: 4, + Delay: 300, + }, "#" + qinput_id); + + // hack, see : https://github.com/autocompletejs/autocomplete.js/issues/37 + w.addEventListener('resize', function() { + var event = new CustomEvent("position"); + qinput.dispatchEvent(event); + }); + } + + qinput.addEventListener('focus', placeCursorAtEndOnce, false); + qinput.focus(); + } + + // vanilla js version of search_on_category_select.js + if (qinput !== null && searx.search_on_category_select) { + d.querySelector('.help').className='invisible'; + + searx.on('#categories input', 'change', function(e) { + var i, categories = d.querySelectorAll('#categories input[type="checkbox"]'); + for(i=0; i=e.From&&a.keyCode<=e.To?!e.Not:e.Not))}),!0===s&&r.Callback.call(e,a)}},t.prototype.makeRequest=function(t,e){var n=Object.getOwnPropertyNames(t.HttpHeaders),o=new XMLHttpRequest,a=t._HttpMethod(),i=t._Url(),r=t._Pre(),s=encodeURIComponent(t._QueryArg())+"="+encodeURIComponent(r);a.match(/^GET$/i)&&(-1!==i.indexOf("?")?i+="&"+s:i+="?"+s),o.open(a,i,!0);for(var l=n.length-1;l>=0;l--)o.setRequestHeader(n[l],t.HttpHeaders[n[l]]);return o.onreadystatechange=function(){4==o.readyState&&200==o.status&&(t.$Cache[r]=o.response,e(o.response))},o},t.prototype.ajax=function(e,n,o){void 0===o&&(o=!0),e.$AjaxTimer&&window.clearTimeout(e.$AjaxTimer),!0===o?e.$AjaxTimer=window.setTimeout(t.prototype.ajax.bind(null,e,n,!1),e.Delay):(e.Request&&e.Request.abort(),e.Request=n,e.Request.send(e._QueryArg()+"="+e._Pre()))},t.prototype.cache=function(e,n){var o=e._Cache(e._Pre());if(void 0===o){var a=t.prototype.makeRequest(e,n);t.prototype.ajax(e,a)}else n(o)},t.prototype.destroy=function(t){for(var e in t.$Listeners)t.Input.removeEventListener(e,t.$Listeners[e]);t.DOMResults.parentNode.removeChild(t.DOMResults)},t}();i.merge=function(){for(var t,e={},n=0;n"+t+""}},HttpHeaders:{"Content-type":"application/x-www-form-urlencoded"},Limit:0,MinChars:0,HttpMethod:"GET",QueryArg:"q",Url:null,KeyboardMappings:{Enter:{Conditions:[{Is:13,Not:!1}],Callback:function(t){if(-1!=this.DOMResults.getAttribute("class").indexOf("open")){var e=this.DOMResults.querySelector("li.active");null!==e&&(t.preventDefault(),this._Select(e),this.DOMResults.setAttribute("class","autocomplete"))}},Operator:o.AND,Event:a.KEYDOWN},KeyUpAndDown_down:{Conditions:[{Is:38,Not:!1},{Is:40,Not:!1}],Callback:function(t){t.preventDefault()},Operator:o.OR,Event:a.KEYDOWN},KeyUpAndDown_up:{Conditions:[{Is:38,Not:!1},{Is:40,Not:!1}],Callback:function(t){t.preventDefault();var e=this.DOMResults.querySelector("li:first-child:not(.locked)"),n=this.DOMResults.querySelector("li:last-child:not(.locked)"),o=this.DOMResults.querySelector("li.active");if(o){var a=Array.prototype.indexOf.call(o.parentNode.children,o)+(t.keyCode-39),i=this.DOMResults.getElementsByTagName("li").length;a<0?a=i-1:a>=i&&(a=0),o.classList.remove("active"),o.parentElement.children.item(a).classList.add("active")}else n&&38==t.keyCode?n.classList.add("active"):e&&e.classList.add("active")},Operator:o.OR,Event:a.KEYUP},AlphaNum:{Conditions:[{Is:13,Not:!0},{From:35,To:40,Not:!0}],Callback:function(){var t=this.Input.getAttribute("data-autocomplete-old-value"),e=this._Pre();""!==e&&e.length>=this._MinChars()&&(t&&e==t||this.DOMResults.setAttribute("class","autocomplete open"),i.prototype.cache(this,function(t){this._Render(this._Post(t)),this._Open()}.bind(this)))},Operator:o.AND,Event:a.KEYUP}},DOMResults:null,Request:null,Input:null,_EmptyMessage:function(){return this.Input.hasAttribute("data-autocomplete-empty-message")?this.Input.getAttribute("data-autocomplete-empty-message"):!1!==this.EmptyMessage?this.EmptyMessage:""},_Limit:function(){var t=this.Input.getAttribute("data-autocomplete-limit");return isNaN(t)||null===t?this.Limit:parseInt(t,10)},_MinChars:function(){var t=this.Input.getAttribute("data-autocomplete-minchars");return isNaN(t)||null===t?this.MinChars:parseInt(t,10)},_Highlight:function(t){return t.replace(this.Highlight.getRegex(this._Pre()),this.Highlight.transform)},_HttpMethod:function(){return this.Input.hasAttribute("data-autocomplete-method")?this.Input.getAttribute("data-autocomplete-method"):this.HttpMethod},_QueryArg:function(){return this.Input.hasAttribute("data-autocomplete-param-name")?this.Input.getAttribute("data-autocomplete-param-name"):this.QueryArg},_Url:function(){return this.Input.hasAttribute("data-autocomplete")?this.Input.getAttribute("data-autocomplete"):this.Url},_Blur:function(t){if(!0===t)this.DOMResults.setAttribute("class","autocomplete"),this.Input.setAttribute("data-autocomplete-old-value",this.Input.value);else{var e=this;setTimeout(function(){e._Blur(!0)},150)}},_Cache:function(t){return this.$Cache[t]},_Focus:function(){var t=this.Input.getAttribute("data-autocomplete-old-value");(!t||this.Input.value!=t)&&this._MinChars()<=this.Input.value.length&&this.DOMResults.setAttribute("class","autocomplete open")},_Open:function(){var t=this;Array.prototype.forEach.call(this.DOMResults.getElementsByTagName("li"),function(e){"locked"!=e.getAttribute("class")&&(e.onclick=function(n){t._Select(e)},e.onmouseenter=function(){var n=t.DOMResults.querySelector("li.active");n!==e&&(null!==n&&n.classList.remove("active"),e.classList.add("active"))})})},_Position:function(){this.DOMResults.setAttribute("class","autocomplete"),this.DOMResults.setAttribute("style","top:"+(this.Input.offsetTop+this.Input.offsetHeight)+"px;left:"+this.Input.offsetLeft+"px;width:"+this.Input.clientWidth+"px;")},_Render:function(t){var e;e="string"==typeof t?this._RenderRaw(t):this._RenderResponseItems(t),this.DOMResults.hasChildNodes()&&this.DOMResults.removeChild(this.DOMResults.childNodes[0]),this.DOMResults.appendChild(e)},_RenderResponseItems:function(t){var e=document.createElement("ul"),n=document.createElement("li"),o=this._Limit();o<0?t=t.reverse():0===o&&(o=t.length);for(var a=0;a0)this.DOMResults.innerHTML=t;else{var o=this._EmptyMessage();""!==o&&(n.innerHTML=o,n.setAttribute("class","locked"),e.appendChild(n))}return e},_Post:function(t){try{var e=[],n=JSON.parse(t);if(0===Object.keys(n).length)return"";if(Array.isArray(n))for(var o=0;o0&&n.naturalHeight>0?o+=n.naturalWidth/n.naturalHeight:o+=1;return e/o},n.prototype._setSize=function(t,e){for(var n,o,a=t.length,i=0;i0&&n.naturalHeight>0?e*n.naturalWidth/n.naturalHeight:e,n.style.width=o+"px",n.style.height=e+"px",n.style.marginLeft="3px",n.style.marginTop="3px",n.style.marginRight=this.margin-7+"px",n.style.marginBottom=this.margin-7+"px"},n.prototype._alignImgs=function(t){var n,o,a=e.querySelector(this.container_selector).clientWidth;t:for(;t.length>0;){for(var i=1;i<=t.length;i++)if(n=t.slice(0,i),(o=this._getHeigth(n,a))0&&(this._alignImgs(r),r=[]),r.push(i.querySelector(this.img_selector)),a=i;r.length>0&&this._alignImgs(r)},n.prototype.watch=function(){function n(t){i._alignAllDone&&(i._alignAllDone=!1,setTimeout(function(){i.align(),i._alignAllDone=!0},100))}var o,a,i=this,r=e.querySelectorAll(this.results_selector),s=r.length;for(t.addEventListener("resize",n),t.addEventListener("pageshow",function(t){i.align()}),o=0;os)break}break;case"down":null===(i=o.nextElementSibling)&&(i=r[0]);break;case"up":null===(i=o.previousElementSibling)&&(i=r[r.length-1]);break;case"bottom":i=r[r.length-1];break;case"top":default:i=r[0]}if(i){o.removeAttribute("data-vim-selected"),i.setAttribute("data-vim-selected","true");var d=i.querySelector("h3 a")||i.querySelector("a");null!==d&&d.focus(),e||n()}}}function e(t){return function(){var e=$('div#pagination button[type="submit"]');2===e.length?t>=0&&to-120?window.scroll(window.scrollX,o-120):e+n'),i+='
',i+='
',i+='
'+l[0].cat+"
",i+='
',i+='
    ';for(var d in l)i+="
  • "+l[d].key+" "+l[d].des+"
  • ";i+="
",i+="
",i+="
",i+="
",c&&!u||(i+="")}i+="",i+="",$("body").append(i)}}},des:"toggle help window",cat:"Other"}};searx.on(document,"keyup",function(t){if(r.hasOwnProperty(t.keyCode)&&!t.ctrlKey&&!t.altKey&&!t.shiftKey&&!t.metaKey){var e=t.target.tagName.toLowerCase();27===t.keyCode?"input"!==e&&"select"!==e&&"textarea"!==e||r[t.keyCode].fun():t.target!==document.body&&"a"!==e&&"button"!==e||r[t.keyCode].fun()}})}),function(t,e,n){"use strict";n.ready(function(){n.on(".searx_overpass_request","click",function(t){this.classList.remove("searx_overpass_request");var o="https://overpass-api.de/api/interpreter?data=[out:json][timeout:25];(",a=this.dataset.osmId,i=this.dataset.osmType,r=e.querySelector("#"+this.dataset.resultTable),s=e.querySelector("#"+this.dataset.resultTableLoadicon),l=["addr:city","addr:country","addr:housenumber","addr:postcode","addr:street"];if(a&&i&&r){var u=null;switch(i){case"node":u=o+"node("+a+"););out meta;";break;case"way":u=o+"way("+a+"););out meta;";break;case"relation":u=o+"relation("+a+"););out meta;"}u&&n.http("GET",u).then(function(t,e){if((t=JSON.parse(t))&&t.elements&&t.elements[0]){var n=t.elements[0],o="";for(var a in n.tags)if(null===n.tags.name||-1==l.indexOf(a)){switch(o+=""+a+"",a){case"phone":case"fax":o+=''+n.tags[a]+"";break;case"email":o+=''+n.tags[a]+"";break;case"website":case"url":o+=''+n.tags[a]+"";break;case"wikidata":o+=''+n.tags[a]+"";break;case"wikipedia":if(-1!=n.tags[a].indexOf(":")){o+=''+n.tags[a]+"";break}default:o+=n.tags[a]}o+=""}s.classList.add("invisible"),r.classList.remove("invisible"),r.querySelector("tbody").innerHTML=o}}).catch(function(){s.innerHTML=s.innerHTML+'

could not load data!

'})}t.preventDefault()}),n.on(".searx_init_map","click",function(t){this.classList.remove("searx_init_map");var e=this.dataset.leafletTarget,o=parseFloat(this.dataset.mapLon),a=parseFloat(this.dataset.mapLat),i=parseFloat(this.dataset.mapZoom),r=JSON.parse(this.dataset.mapBoundingbox),s=JSON.parse(this.dataset.mapGeojson);n.loadStyle("leaflet/leaflet.css"),n.loadScript("leaflet/leaflet.js",function(){var t=null;if(r){var n=L.latLng(r[0],r[2]),l=L.latLng(r[1],r[3]);t=L.latLngBounds(n,l)}var u=L.map(e),c=new L.TileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",{minZoom:1,maxZoom:19,attribution:'Map data © OpenStreetMap contributors'});new L.TileLayer("https://maps.wikimedia.org/osm-intl/{z}/{x}/{y}.png",{minZoom:1,maxZoom:19,attribution:'Wikimedia maps beta | Maps data © OpenStreetMap contributors'});t?setTimeout(function(){u.fitBounds(t,{maxZoom:17})},0):o&&a&&(i?u.setView(new L.latLng(a,o),i):u.setView(new L.latLng(a,o),8)),u.addLayer(c);var d={"OSM Mapnik":c};L.control.layers(d).addTo(u),s&&L.geoJson(s).addTo(u)}),t.preventDefault()})})}(window,document,window.searx),function(t,e,n){"use strict";n.ready(function(){n.image_thumbnail_layout=new n.ImageLayout("#urls","#urls .result-images","img.image_thumbnail",200),n.image_thumbnail_layout.watch(),n.on(".btn-collapse","click",function(t){var n=this.getAttribute("data-btn-text-collapsed"),o=this.getAttribute("data-btn-text-not-collapsed"),a=this.getAttribute("data-target"),i=e.querySelector(a),r=this.innerHTML;r=this.classList.contains("collapsed")?r.replace(n,o):r.replace(o,n),this.innerHTML=r,this.classList.toggle("collapsed"),i.classList.toggle("invisible")}),n.on(".media-loader","click",function(t){var n=this.getAttribute("data-target"),o=e.querySelector(n+" > iframe"),a=o.getAttribute("src");null!==a&&void 0!==a&&!1!==a||o.setAttribute("src",o.getAttribute("data-src"))}),t.addEventListener("scroll",function(){var t=e.getElementById("backToTop"),n=document.documentElement.scrollTop||document.body.scrollTop;null!==t&&(t.style.opacity=n>=200?1:0)})})}(window,document,window.searx),function(t,e,n){"use strict";function o(t){if(t.setSelectionRange){var e=t.value.length;t.setSelectionRange(e,e)}}function a(){if(i.value.length>0){var t=document.getElementById("search");setTimeout(t.submit.bind(t),0)}}var i,r=!0;n.ready(function(){null!==(i=e.getElementById("q"))&&(n.autocompleter&&(n.autocomplete=AutoComplete.call(t,{Url:"./autocompleter",EmptyMessage:n.noItemFound,HttpMethod:n.method,MinChars:4,Delay:300},"#q"),t.addEventListener("resize",function(){var t=new CustomEvent("position");i.dispatchEvent(t)})),i.addEventListener("focus",function(t){r&&(o(i),r=!1)},!1),i.focus()),null!==i&&n.search_on_category_select&&(e.querySelector(".help").className="invisible",n.on("#categories input","change",function(t){var n,o=e.querySelectorAll('#categories input[type="checkbox"]');for(n=0;n. +* +* (C) 2017 by Alexandre Flament, +* +*/ +(function(w, d, searx) { + + 'use strict'; + + // not invented here tookit with bugs fixed elsewhere + // purposes : be just good enough and as small as possible + + // from https://plainjs.com/javascript/events/live-binding-event-handlers-14/ + if (w.Element) { + (function(ElementPrototype) { + ElementPrototype.matches = ElementPrototype.matches || + ElementPrototype.matchesSelector || + ElementPrototype.webkitMatchesSelector || + ElementPrototype.msMatchesSelector || + function(selector) { + var node = this, nodes = (node.parentNode || node.document).querySelectorAll(selector), i = -1; + while (nodes[++i] && nodes[i] != node); + return !!nodes[i]; + }; + })(Element.prototype); + } + + function callbackSafe(callback, el, e) { + try { + callback.call(el, e); + } catch (exception) { + console.log(exception); + } + } + + searx = searx || {}; + + searx.on = function(obj, eventType, callback, useCapture) { + useCapture = useCapture || false; + if (typeof obj !== 'string') { + // obj HTMLElement, HTMLDocument + obj.addEventListener(eventType, callback, useCapture); + } else { + // obj is a selector + d.addEventListener(eventType, function(e) { + var el = e.target || e.srcElement, found = false; + while (el && el.matches && el !== d && !(found = el.matches(obj))) el = el.parentElement; + if (found) callbackSafe(callback, el, e); + }, useCapture); + } + }; + + searx.ready = function(callback) { + if (document.readyState != 'loading') { + callback.call(w); + } else { + w.addEventListener('DOMContentLoaded', callback.bind(w)); + } + }; + + searx.http = function(method, url, callback) { + var req = new XMLHttpRequest(), + resolve = function() {}, + reject = function() {}, + promise = { + then: function(callback) { resolve = callback; return promise; }, + catch: function(callback) { reject = callback; return promise; } + }; + + try { + req.open(method, url, true); + + // On load + req.onload = function() { + if (req.status == 200) { + resolve(req.response, req.responseType); + } else { + reject(Error(req.statusText)); + } + }; + + // Handle network errors + req.onerror = function() { + reject(Error("Network Error")); + }; + + req.onabort = function() { + reject(Error("Transaction is aborted")); + }; + + // Make the request + req.send(); + } catch (ex) { + reject(ex); + } + + return promise; + }; + + searx.loadStyle = function(src) { + var path = searx.staticPath + src, + id = "style_" + src.replace('.', '_'), + s = d.getElementById(id); + if (s === null) { + s = d.createElement('link'); + s.setAttribute('id', id); + s.setAttribute('rel', 'stylesheet'); + s.setAttribute('type', 'text/css'); + s.setAttribute('href', path); + d.body.appendChild(s); + } + }; + + searx.loadScript = function(src, callback) { + var path = searx.staticPath + src, + id = "script_" + src.replace('.', '_'), + s = d.getElementById(id); + if (s === null) { + s = d.createElement('script'); + s.setAttribute('id', id); + s.setAttribute('src', path); + s.onload = callback; + s.onerror = function() { + s.setAttribute('error', '1'); + }; + d.body.appendChild(s); + } else if (!s.hasAttribute('error')) { + try { + callback.apply(s, []); + } catch (exception) { + console.log(exception); + } + } else { + console.log("callback not executed : script '" + path + "' not loaded."); + } + }; + + searx.on('.close', 'click', function(e) { + var el = e.target || e.srcElement; + this.parentNode.style.display="None"; + }); + return searx; +})(window, document, window.searx); diff --git a/searx/static/themes/simple/js/searx_src/autocomplete.js b/searx/static/themes/simple/js/searx_src/autocomplete.js new file mode 100644 index 000000000..b95fbcfb2 --- /dev/null +++ b/searx/static/themes/simple/js/searx_src/autocomplete.js @@ -0,0 +1,536 @@ +(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.AutoComplete = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o @baptistedonaux + */ +var AutoComplete = (function () { + // Constructor + function AutoComplete(params, selector) { + if (params === void 0) { params = {}; } + if (selector === void 0) { selector = "[data-autocomplete]"; } + if (Array.isArray(selector)) { + selector.forEach(function (s) { + new AutoComplete(params, s); + }); + } + else if (typeof selector == "string") { + var elements = document.querySelectorAll(selector); + Array.prototype.forEach.call(elements, function (input) { + new AutoComplete(params, input); + }); + } + else { + var specificParams = AutoComplete.merge(AutoComplete.defaults, params, { + DOMResults: document.createElement("div") + }); + AutoComplete.prototype.create(specificParams, selector); + return specificParams; + } + } + AutoComplete.prototype.create = function (params, element) { + params.Input = element; + if (params.Input.nodeName.match(/^INPUT$/i) && (params.Input.hasAttribute("type") === false || params.Input.getAttribute("type").match(/^TEXT|SEARCH$/i))) { + params.Input.setAttribute("autocomplete", "off"); + params._Position(params); + params.Input.parentNode.appendChild(params.DOMResults); + params.$Listeners = { + blur: params._Blur.bind(params), + destroy: AutoComplete.prototype.destroy.bind(null, params), + focus: params._Focus.bind(params), + keyup: AutoComplete.prototype.event.bind(null, params, EventType.KEYUP), + keydown: AutoComplete.prototype.event.bind(null, params, EventType.KEYDOWN), + position: params._Position.bind(params) + }; + for (var event in params.$Listeners) { + params.Input.addEventListener(event, params.$Listeners[event]); + } + } + }; + AutoComplete.prototype.getEventsByType = function (params, type) { + var mappings = {}; + for (var key in params.KeyboardMappings) { + var event = EventType.KEYUP; + if (params.KeyboardMappings[key].Event !== undefined) { + event = params.KeyboardMappings[key].Event; + } + if (event == type) { + mappings[key] = params.KeyboardMappings[key]; + } + } + return mappings; + }; + AutoComplete.prototype.event = function (params, type, event) { + var eventIdentifier = function (condition) { + if ((match === true && mapping.Operator == ConditionOperator.AND) || (match === false && mapping.Operator == ConditionOperator.OR)) { + condition = AutoComplete.merge({ + Not: false + }, condition); + if (condition.hasOwnProperty("Is")) { + if (condition.Is == event.keyCode) { + match = !condition.Not; + } + else { + match = condition.Not; + } + } + else if (condition.hasOwnProperty("From") && condition.hasOwnProperty("To")) { + if (event.keyCode >= condition.From && event.keyCode <= condition.To) { + match = !condition.Not; + } + else { + match = condition.Not; + } + } + } + }; + for (var name in AutoComplete.prototype.getEventsByType(params, type)) { + var mapping = AutoComplete.merge({ + Operator: ConditionOperator.AND + }, params.KeyboardMappings[name]), match = ConditionOperator.AND == mapping.Operator; + mapping.Conditions.forEach(eventIdentifier); + if (match === true) { + mapping.Callback.call(params, event); + } + } + }; + AutoComplete.prototype.makeRequest = function (params, callback) { + var propertyHttpHeaders = Object.getOwnPropertyNames(params.HttpHeaders), request = new XMLHttpRequest(), method = params._HttpMethod(), url = params._Url(), queryParams = params._Pre(), queryParamsStringify = encodeURIComponent(params._QueryArg()) + "=" + encodeURIComponent(queryParams); + if (method.match(/^GET$/i)) { + if (url.indexOf("?") !== -1) { + url += "&" + queryParamsStringify; + } + else { + url += "?" + queryParamsStringify; + } + } + request.open(method, url, true); + for (var i = propertyHttpHeaders.length - 1; i >= 0; i--) { + request.setRequestHeader(propertyHttpHeaders[i], params.HttpHeaders[propertyHttpHeaders[i]]); + } + request.onreadystatechange = function () { + if (request.readyState == 4 && request.status == 200) { + params.$Cache[queryParams] = request.response; + callback(request.response); + } + }; + return request; + }; + AutoComplete.prototype.ajax = function (params, request, timeout) { + if (timeout === void 0) { timeout = true; } + if (params.$AjaxTimer) { + window.clearTimeout(params.$AjaxTimer); + } + if (timeout === true) { + params.$AjaxTimer = window.setTimeout(AutoComplete.prototype.ajax.bind(null, params, request, false), params.Delay); + } + else { + if (params.Request) { + params.Request.abort(); + } + params.Request = request; + params.Request.send(params._QueryArg() + "=" + params._Pre()); + } + }; + AutoComplete.prototype.cache = function (params, callback) { + var response = params._Cache(params._Pre()); + if (response === undefined) { + var request = AutoComplete.prototype.makeRequest(params, callback); + AutoComplete.prototype.ajax(params, request); + } + else { + callback(response); + } + }; + AutoComplete.prototype.destroy = function (params) { + for (var event in params.$Listeners) { + params.Input.removeEventListener(event, params.$Listeners[event]); + } + params.DOMResults.parentNode.removeChild(params.DOMResults); + }; + return AutoComplete; +}()); +AutoComplete.merge = function () { + var merge = {}, tmp; + for (var i = 0; i < arguments.length; i++) { + for (tmp in arguments[i]) { + merge[tmp] = arguments[i][tmp]; + } + } + return merge; +}; +AutoComplete.defaults = { + Delay: 150, + EmptyMessage: "No result here", + Highlight: { + getRegex: function (value) { + return new RegExp(value, "ig"); + }, + transform: function (value) { + return "" + value + ""; + } + }, + HttpHeaders: { + "Content-type": "application/x-www-form-urlencoded" + }, + Limit: 0, + MinChars: 0, + HttpMethod: "GET", + QueryArg: "q", + Url: null, + KeyboardMappings: { + "Enter": { + Conditions: [{ + Is: 13, + Not: false + }], + Callback: function (event) { + if (this.DOMResults.getAttribute("class").indexOf("open") != -1) { + var liActive = this.DOMResults.querySelector("li.active"); + if (liActive !== null) { + event.preventDefault(); + this._Select(liActive); + this.DOMResults.setAttribute("class", "autocomplete"); + } + } + }, + Operator: ConditionOperator.AND, + Event: EventType.KEYDOWN + }, + "KeyUpAndDown_down": { + Conditions: [{ + Is: 38, + Not: false + }, + { + Is: 40, + Not: false + }], + Callback: function (event) { + event.preventDefault(); + }, + Operator: ConditionOperator.OR, + Event: EventType.KEYDOWN + }, + "KeyUpAndDown_up": { + Conditions: [{ + Is: 38, + Not: false + }, + { + Is: 40, + Not: false + }], + Callback: function (event) { + event.preventDefault(); + var first = this.DOMResults.querySelector("li:first-child:not(.locked)"), last = this.DOMResults.querySelector("li:last-child:not(.locked)"), active = this.DOMResults.querySelector("li.active"); + if (active) { + var currentIndex = Array.prototype.indexOf.call(active.parentNode.children, active), position = currentIndex + (event.keyCode - 39), lisCount = this.DOMResults.getElementsByTagName("li").length; + if (position < 0) { + position = lisCount - 1; + } + else if (position >= lisCount) { + position = 0; + } + active.classList.remove("active"); + active.parentElement.children.item(position).classList.add("active"); + } + else if (last && event.keyCode == 38) { + last.classList.add("active"); + } + else if (first) { + first.classList.add("active"); + } + }, + Operator: ConditionOperator.OR, + Event: EventType.KEYUP + }, + "AlphaNum": { + Conditions: [{ + Is: 13, + Not: true + }, { + From: 35, + To: 40, + Not: true + }], + Callback: function () { + var oldValue = this.Input.getAttribute("data-autocomplete-old-value"), currentValue = this._Pre(); + if (currentValue !== "" && currentValue.length >= this._MinChars()) { + if (!oldValue || currentValue != oldValue) { + this.DOMResults.setAttribute("class", "autocomplete open"); + } + AutoComplete.prototype.cache(this, function (response) { + this._Render(this._Post(response)); + this._Open(); + }.bind(this)); + } + }, + Operator: ConditionOperator.AND, + Event: EventType.KEYUP + } + }, + DOMResults: null, + Request: null, + Input: null, + /** + * Return the message when no result returns + */ + _EmptyMessage: function () { + var emptyMessage = ""; + if (this.Input.hasAttribute("data-autocomplete-empty-message")) { + emptyMessage = this.Input.getAttribute("data-autocomplete-empty-message"); + } + else if (this.EmptyMessage !== false) { + emptyMessage = this.EmptyMessage; + } + else { + emptyMessage = ""; + } + return emptyMessage; + }, + /** + * Returns the maximum number of results + */ + _Limit: function () { + var limit = this.Input.getAttribute("data-autocomplete-limit"); + if (isNaN(limit) || limit === null) { + return this.Limit; + } + return parseInt(limit, 10); + }, + /** + * Returns the minimum number of characters entered before firing ajax + */ + _MinChars: function () { + var minchars = this.Input.getAttribute("data-autocomplete-minchars"); + if (isNaN(minchars) || minchars === null) { + return this.MinChars; + } + return parseInt(minchars, 10); + }, + /** + * Apply transformation on labels response + */ + _Highlight: function (label) { + return label.replace(this.Highlight.getRegex(this._Pre()), this.Highlight.transform); + }, + /** + * Returns the HHTP method to use + */ + _HttpMethod: function () { + if (this.Input.hasAttribute("data-autocomplete-method")) { + return this.Input.getAttribute("data-autocomplete-method"); + } + return this.HttpMethod; + }, + /** + * Returns the query param to use + */ + _QueryArg: function () { + if (this.Input.hasAttribute("data-autocomplete-param-name")) { + return this.Input.getAttribute("data-autocomplete-param-name"); + } + return this.QueryArg; + }, + /** + * Returns the URL to use for AJAX request + */ + _Url: function () { + if (this.Input.hasAttribute("data-autocomplete")) { + return this.Input.getAttribute("data-autocomplete"); + } + return this.Url; + }, + /** + * Manage the close + */ + _Blur: function (now) { + if (now === true) { + this.DOMResults.setAttribute("class", "autocomplete"); + this.Input.setAttribute("data-autocomplete-old-value", this.Input.value); + } + else { + var params = this; + setTimeout(function () { + params._Blur(true); + }, 150); + } + }, + /** + * Manage the cache + */ + _Cache: function (value) { + return this.$Cache[value]; + }, + /** + * Manage the open + */ + _Focus: function () { + var oldValue = this.Input.getAttribute("data-autocomplete-old-value"); + if ((!oldValue || this.Input.value != oldValue) && this._MinChars() <= this.Input.value.length) { + this.DOMResults.setAttribute("class", "autocomplete open"); + } + }, + /** + * Bind all results item if one result is opened + */ + _Open: function () { + var params = this; + Array.prototype.forEach.call(this.DOMResults.getElementsByTagName("li"), function (li) { + if (li.getAttribute("class") != "locked") { + li.onclick = function (event) { + params._Select(li); + }; + li.onmouseenter = function () { + var active = params.DOMResults.querySelector("li.active"); + if (active !== li) { + if (active !== null) { + active.classList.remove("active"); + } + li.classList.add("active"); + } + }; + } + }); + }, + /** + * Position the results HTML element + */ + _Position: function () { + this.DOMResults.setAttribute("class", "autocomplete"); + this.DOMResults.setAttribute("style", "top:" + (this.Input.offsetTop + this.Input.offsetHeight) + "px;left:" + this.Input.offsetLeft + "px;width:" + this.Input.clientWidth + "px;"); + }, + /** + * Execute the render of results DOM element + */ + _Render: function (response) { + var ul; + if (typeof response == "string") { + ul = this._RenderRaw(response); + } + else { + ul = this._RenderResponseItems(response); + } + if (this.DOMResults.hasChildNodes()) { + this.DOMResults.removeChild(this.DOMResults.childNodes[0]); + } + this.DOMResults.appendChild(ul); + }, + /** + * ResponseItems[] rendering + */ + _RenderResponseItems: function (response) { + var ul = document.createElement("ul"), li = document.createElement("li"), limit = this._Limit(); + // Order + if (limit < 0) { + response = response.reverse(); + } + else if (limit === 0) { + limit = response.length; + } + for (var item = 0; item < Math.min(Math.abs(limit), response.length); item++) { + li.innerHTML = response[item].Label; + li.setAttribute("data-autocomplete-value", response[item].Value); + ul.appendChild(li); + li = document.createElement("li"); + } + return ul; + }, + /** + * string response rendering (RAW HTML) + */ + _RenderRaw: function (response) { + var ul = document.createElement("ul"), li = document.createElement("li"); + if (response.length > 0) { + this.DOMResults.innerHTML = response; + } + else { + var emptyMessage = this._EmptyMessage(); + if (emptyMessage !== "") { + li.innerHTML = emptyMessage; + li.setAttribute("class", "locked"); + ul.appendChild(li); + } + } + return ul; + }, + /** + * Deal with request response + */ + _Post: function (response) { + try { + var returnResponse = []; + //JSON return + var json = JSON.parse(response); + if (Object.keys(json).length === 0) { + return ""; + } + if (Array.isArray(json)) { + for (var i = 0; i < Object.keys(json).length; i++) { + returnResponse[returnResponse.length] = { "Value": json[i], "Label": this._Highlight(json[i]) }; + } + } + else { + for (var value in json) { + returnResponse.push({ + "Value": value, + "Label": this._Highlight(json[value]) + }); + } + } + return returnResponse; + } + catch (event) { + //HTML return + return response; + } + }, + /** + * Return the autocomplete value to send (before request) + */ + _Pre: function () { + return this.Input.value; + }, + /** + * Choice one result item + */ + _Select: function (item) { + console.log('test test test'); + if (item.hasAttribute("data-autocomplete-value")) { + this.Input.value = item.getAttribute("data-autocomplete-value"); + } + else { + this.Input.value = item.innerHTML; + } + this.Input.setAttribute("data-autocomplete-old-value", this.Input.value); + }, + $AjaxTimer: null, + $Cache: {}, + $Listeners: {} +}; +module.exports = AutoComplete; + +},{}]},{},[1])(1) +}); diff --git a/searx/static/themes/simple/js/searx_src/searx_imageresult.js b/searx/static/themes/simple/js/searx_src/searx_imageresult.js new file mode 100644 index 000000000..7bbfc1454 --- /dev/null +++ b/searx/static/themes/simple/js/searx_src/searx_imageresult.js @@ -0,0 +1,151 @@ +/** +* +* Google Image Layout v0.0.1 +* Description, by Anh Trinh. +* Heavily modified for searx +* http://trinhtrunganh.com +* +* @license Free to use under the MIT License. +* +*/ +(function(w, d) { + 'use strict'; + + function ImageLayout(container_selector, results_selector, img_selector, maxHeight) { + this.container_selector = container_selector; + this.results_selector = results_selector; + this.img_selector = img_selector; + this.margin = 10; + this.maxHeight = maxHeight; + this._alignAllDone = true; + } + + /** + * Get the height that make all images fit the container + * + * width = w1 + w2 + w3 + ... = r1*h + r2*h + r3*h + ... + * + * @param {[type]} images the images to be calculated + * @param {[type]} width the container witdth + * @param {[type]} margin the margin between each image + * + * @return {[type]} the height + */ + ImageLayout.prototype._getHeigth = function(images, width) { + var r = 0, + img; + + width -= images.length * this.margin; + for (var i = 0; i < images.length; i++) { + img = images[i]; + if ((img.naturalWidth > 0) && (img.naturalHeight > 0)) { + r += img.naturalWidth / img.naturalHeight; + } else { + // assume that not loaded images are square + r += 1; + } + } + + return width / r; //have to round down because Firefox will automatically roundup value with number of decimals > 3 + }; + + ImageLayout.prototype._setSize = function(images, height) { + var img, imgWidth, imagesLength = images.length; + for (var i = 0; i < imagesLength; i++) { + img = images[i]; + if ((img.naturalWidth > 0) && (img.naturalHeight > 0)) { + imgWidth = height * img.naturalWidth / img.naturalHeight; + } else { + // not loaded image : make it square as _getHeigth said it + imgWidth = height; + } + img.style.width = imgWidth + 'px'; + img.style.height = height + 'px'; + img.style.marginLeft = '3px'; + img.style.marginTop = '3px'; + img.style.marginRight = this.margin - 7 + 'px'; // -4 is the negative margin of the inline element + img.style.marginBottom = this.margin - 7 + 'px'; + } + }; + + ImageLayout.prototype._alignImgs = function(imgGroup) { + var slice, h, + containerWidth = d.querySelector(this.container_selector).clientWidth; + + w: while (imgGroup.length > 0) { + for (var i = 1; i <= imgGroup.length; i++) { + slice = imgGroup.slice(0, i); + h = this._getHeigth(slice, containerWidth); + if (h < this.maxHeight) { + this._setSize(slice, h); + imgGroup = imgGroup.slice(i); + continue w; + } + } + this._setSize(slice, Math.min(this.maxHeight, h)); + break; + } + }; + + ImageLayout.prototype.align = function(results_selector) { + var results_selectorNode = d.querySelectorAll(this.results_selector), + results_length = results_selectorNode.length, + previous = null, + current = null, + imgGroup = []; + for (var i = 0; i < results_length; i++) { + current = results_selectorNode[i]; + if (current.previousElementSibling !== previous && imgGroup.length > 0) { + // the current image is not conected to previous one + // so the current image is the start of a new group of images. + // so call _alignImgs to align the current group + this._alignImgs(imgGroup); + // and start a new empty group of images + imgGroup = []; + } + // add the current image to the group (only the img tag) + imgGroup.push(current.querySelector(this.img_selector)); + // update the previous variable + previous = current; + } + // align the remaining images + if (imgGroup.length > 0) { + this._alignImgs(imgGroup); + } + }; + + ImageLayout.prototype.watch = function() { + var i, img, imgGroup, imgNodeLength, + obj = this, + results_nodes = d.querySelectorAll(this.results_selector), + results_length = results_nodes.length; + + function align(e) { + obj.align(); + } + + function throttleAlign(e) { + if (obj._alignAllDone) { + obj._alignAllDone = false; + setTimeout(function() { + obj.align(); + obj._alignAllDone = true; + }, 100); + } + } + + w.addEventListener('resize', throttleAlign); + w.addEventListener('pageshow', align); + + for (i = 0; i < results_length; i++) { + img = results_nodes[i].querySelector(this.img_selector); + if (typeof img !== 'undefined') { + img.addEventListener('load', throttleAlign); + img.addEventListener('error', throttleAlign); + } + } + }; + + w.searx.ImageLayout = ImageLayout; + +})(window, document); diff --git a/searx/static/themes/simple/js/searx_src/searx_keyboard.js b/searx/static/themes/simple/js/searx_src/searx_keyboard.js new file mode 100644 index 000000000..6365b5243 --- /dev/null +++ b/searx/static/themes/simple/js/searx_src/searx_keyboard.js @@ -0,0 +1,360 @@ +searx.ready(function() { + + searx.on('.result', 'click', function() { + highlightResult(this)(true); + }); + + searx.on('.result a', 'focus', function(e) { + var el = e.target; + while (el !== undefined) { + if (el.classList.contains('result')) { + if (el.getAttribute("data-vim-selected") === null) { + highlightResult(el)(true); + } + break; + } + el = el.parentNode; + } + }, true); + + var vimKeys = { + 27: { + key: 'Escape', + fun: removeFocus, + des: 'remove focus from the focused input', + cat: 'Control' + }, + 73: { + key: 'i', + fun: searchInputFocus, + des: 'focus on the search input', + cat: 'Control' + }, + 66: { + key: 'b', + fun: scrollPage(-window.innerHeight), + des: 'scroll one page up', + cat: 'Navigation' + }, + 70: { + key: 'f', + fun: scrollPage(window.innerHeight), + des: 'scroll one page down', + cat: 'Navigation' + }, + 85: { + key: 'u', + fun: scrollPage(-window.innerHeight / 2), + des: 'scroll half a page up', + cat: 'Navigation' + }, + 68: { + key: 'd', + fun: scrollPage(window.innerHeight / 2), + des: 'scroll half a page down', + cat: 'Navigation' + }, + 71: { + key: 'g', + fun: scrollPageTo(-document.body.scrollHeight, 'top'), + des: 'scroll to the top of the page', + cat: 'Navigation' + }, + 86: { + key: 'v', + fun: scrollPageTo(document.body.scrollHeight, 'bottom'), + des: 'scroll to the bottom of the page', + cat: 'Navigation' + }, + 75: { + key: 'k', + fun: highlightResult('up'), + des: 'select previous search result', + cat: 'Results' + }, + 74: { + key: 'j', + fun: highlightResult('down'), + des: 'select next search result', + cat: 'Results' + }, + 80: { + key: 'p', + fun: pageButtonClick(0), + des: 'go to previous page', + cat: 'Results' + }, + 78: { + key: 'n', + fun: pageButtonClick(1), + des: 'go to next page', + cat: 'Results' + }, + 79: { + key: 'o', + fun: openResult(false), + des: 'open search result', + cat: 'Results' + }, + 84: { + key: 't', + fun: openResult(true), + des: 'open the result in a new tab', + cat: 'Results' + }, + 82: { + key: 'r', + fun: reloadPage, + des: 'reload page from the server', + cat: 'Control' + }, + 72: { + key: 'h', + fun: toggleHelp, + des: 'toggle help window', + cat: 'Other' + } + }; + + searx.on(document, "keyup", function(e) { + // check for modifiers so we don't break browser's hotkeys + if (vimKeys.hasOwnProperty(e.keyCode) && !e.ctrlKey && !e.altKey && !e.shiftKey && !e.metaKey) { + var tagName = e.target.tagName.toLowerCase(); + if (e.keyCode === 27) { + if (tagName === 'input' || tagName === 'select' || tagName === 'textarea') { + vimKeys[e.keyCode].fun(); + } + } else { + if (e.target === document.body || tagName === 'a' || tagName === 'button') { + vimKeys[e.keyCode].fun(); + } + } + } + }); + + function highlightResult(which) { + return function(noScroll) { + var current = document.querySelector('.result[data-vim-selected]'), + effectiveWhich = which; + if (current === null) { + // no selection : choose the first one + current = document.querySelector('.result'); + if (current === null) { + // no first one : there are no results + return; + } + // replace up/down actions by selecting first one + if (which === "down" || which === "up") { + effectiveWhich = current; + } + } + + var next, results = document.querySelectorAll('.result'); + + if (typeof effectiveWhich !== 'string') { + next = effectiveWhich; + } else { + switch (effectiveWhich) { + case 'visible': + var top = document.documentElement.scrollTop || document.body.scrollTop; + var bot = top + document.documentElement.clientHeight; + + for (var i = 0; i < results.length; i++) { + next = results[i]; + var etop = next.offsetTop; + var ebot = etop + next.clientHeight; + + if ((ebot <= bot) && (etop > top)) { + break; + } + } + break; + case 'down': + next = current.nextElementSibling; + if (next === null) { + next = results[0]; + } + break; + case 'up': + next = current.previousElementSibling; + if (next === null) { + next = results[results.length - 1]; + } + break; + case 'bottom': + next = results[results.length - 1]; + break; + case 'top': + /* falls through */ + default: + next = results[0]; + } + } + + if (next) { + current.removeAttribute('data-vim-selected'); + next.setAttribute('data-vim-selected', 'true'); + var link = next.querySelector('h3 a') || next.querySelector('a'); + if (link !== null) { + link.focus(); + } + if (!noScroll) { + scrollPageToSelected(); + } + } + }; + } + + function reloadPage() { + document.location.reload(true); + } + + function removeFocus() { + if (document.activeElement) { + document.activeElement.blur(); + } + } + + function pageButtonClick(num) { + return function() { + var buttons = $('div#pagination button[type="submit"]'); + if (buttons.length !== 2) { + console.log('page navigation with this theme is not supported'); + return; + } + if (num >= 0 && num < buttons.length) { + buttons[num].click(); + } else { + console.log('pageButtonClick(): invalid argument'); + } + }; + } + + function scrollPageToSelected() { + var sel = document.querySelector('.result[data-vim-selected]'); + if (sel === null) { + return; + } + var wtop = document.documentElement.scrollTop || document.body.scrollTop, + wheight = document.documentElement.clientHeight, + etop = sel.offsetTop, + ebot = etop + sel.clientHeight, + offset = 120; + // first element ? + if ((sel.previousElementSibling === null) && (ebot < wheight)) { + // set to the top of page if the first element + // is fully included in the viewport + window.scroll(window.scrollX, 0); + return; + } + if (wtop > (etop - offset)) { + window.scroll(window.scrollX, etop - offset); + } else { + var wbot = wtop + wheight; + if (wbot < (ebot + offset)) { + window.scroll(window.scrollX, ebot - wheight + offset); + } + } + } + + function scrollPage(amount) { + return function() { + window.scrollBy(0, amount); + highlightResult('visible')(); + }; + } + + function scrollPageTo(position, nav) { + return function() { + window.scrollTo(0, position); + highlightResult(nav)(); + }; + } + + function searchInputFocus() { + window.scrollTo(0, 0); + document.querySelector('#q').focus(); + } + + function openResult(newTab) { + return function() { + var link = document.querySelector('.result[data-vim-selected] h3 a'); + if (link !== null) { + var url = link.getAttribute('href'); + if (newTab) { + window.open(url); + } else { + window.location.href = url; + } + } + }; + } + + function toggleHelp() { + var helpPanel = document.querySelector('#vim-hotkeys-help'); + if (helpPanel.length) { + helpPanel.classList.toggle('hidden'); + return; + } + + var categories = {}; + + for (var k in vimKeys) { + var key = vimKeys[k]; + categories[key.cat] = categories[key.cat] || []; + categories[key.cat].push(key); + } + + var sorted = Object.keys(categories).sort(function(a, b) { + return categories[b].length - categories[a].length; + }); + + if (sorted.length === 0) { + return; + } + + var html = '
'; + html += '
'; + + html += '
'; + html += '
'; + html += '

How to navigate searx with Vim-like hotkeys

'; + html += '
'; // col-sm-12 + html += '
'; // row + + for (var i = 0; i < sorted.length; i++) { + var cat = categories[sorted[i]]; + + var lastCategory = i === (sorted.length - 1); + var first = i % 2 === 0; + + if (first) { + html += '
'; + } + html += '
'; + + html += '
'; + html += '
' + cat[0].cat + '
'; + html += '
'; + html += '
    '; + + for (var cj in cat) { + html += '
  • ' + cat[cj].key + ' ' + cat[cj].des + '
  • '; + } + + html += '
'; + html += '
'; // panel-body + html += '
'; // panel + html += '
'; // col-sm-* + + if (!first || lastCategory) { + html += '
'; // row + } + } + + html += '
'; // container-fluid + html += '
'; // vim-hotkeys-help + + $('body').append(html); + } +}); diff --git a/searx/static/themes/simple/js/searx_src/searx_mapresult.js b/searx/static/themes/simple/js/searx_src/searx_mapresult.js new file mode 100644 index 000000000..823f64815 --- /dev/null +++ b/searx/static/themes/simple/js/searx_src/searx_mapresult.js @@ -0,0 +1,175 @@ +/** +* searx is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* searx is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with searx. If not, see < http://www.gnu.org/licenses/ >. +* +* (C) 2014 by Thomas Pointhuber, +* (C) 2017 by Alexandre Flament, +*/ +(function (w, d, searx) { + 'use strict'; + + searx.ready(function () { + searx.on('.searx_overpass_request', 'click', function(event) { + // no more request + this.classList.remove("searx_overpass_request"); + + // + var overpass_url = "https://overpass-api.de/api/interpreter?data="; + var query_start = overpass_url + "[out:json][timeout:25];("; + var query_end = ");out meta;"; + + var osm_id = this.dataset.osmId; + var osm_type = this.dataset.osmType; + var result_table = d.querySelector("#" + this.dataset.resultTable); + var result_table_loadicon = d.querySelector("#" + this.dataset.resultTableLoadicon); + + // tags which can be ignored + var osm_ignore_tags = [ "addr:city", "addr:country", "addr:housenumber", "addr:postcode", "addr:street" ]; + + if(osm_id && osm_type && result_table) { + var query = null; + switch(osm_type) { + case 'node': + query = query_start + "node(" + osm_id + ");" + query_end; + break; + case 'way': + query = query_start + "way(" + osm_id + ");" + query_end; + break; + case 'relation': + query = query_start + "relation(" + osm_id + ");" + query_end; + break; + default: + break; + } + if(query) { + // console.log(query); + searx.http( 'GET', query ).then(function(html, contentType) { + html = JSON.parse(html); + if(html && html.elements && html.elements[0]) { + var element = html.elements[0]; + var newHtml = ""; + for (var row in element.tags) { + if(element.tags.name === null || osm_ignore_tags.indexOf(row) == -1) { + newHtml += "" + row + ""; + switch(row) { + case "phone": + case "fax": + newHtml += "" + element.tags[row] + ""; + break; + case "email": + newHtml += "" + element.tags[row] + ""; + break; + case "website": + case "url": + newHtml += "" + element.tags[row] + ""; + break; + case "wikidata": + newHtml += "" + element.tags[row] + ""; + break; + case "wikipedia": + if(element.tags[row].indexOf(":") != -1) { + newHtml += "" + element.tags[row] + ""; + break; + } + /* jshint ignore:start */ + default: + /* jshint ignore:end */ + newHtml += element.tags[row]; + break; + } + newHtml += ""; + } + } + result_table_loadicon.classList.add('invisible'); + result_table.classList.remove('invisible'); + result_table.querySelector("tbody").innerHTML = newHtml; + } + }) + .catch(function() { + result_table_loadicon.innerHTML = result_table_loadicon.innerHTML + "

could not load data!

"; + }); + } + } + + // this event occour only once per element + event.preventDefault(); + }); + + searx.on('.searx_init_map', 'click', function(event) { + // no more request + this.classList.remove("searx_init_map"); + + // + var leaflet_target = this.dataset.leafletTarget; + var map_lon = parseFloat(this.dataset.mapLon); + var map_lat = parseFloat(this.dataset.mapLat); + var map_zoom = parseFloat(this.dataset.mapZoom); + var map_boundingbox = JSON.parse(this.dataset.mapBoundingbox); + var map_geojson = JSON.parse(this.dataset.mapGeojson); + + searx.loadStyle('leaflet/leaflet.css'); + searx.loadScript('leaflet/leaflet.js', function() { + var map_bounds = null; + if(map_boundingbox) { + var southWest = L.latLng(map_boundingbox[0], map_boundingbox[2]); + var northEast = L.latLng(map_boundingbox[1], map_boundingbox[3]); + map_bounds = L.latLngBounds(southWest, northEast); + } + + // init map + var map = L.map(leaflet_target); + // create the tile layer with correct attribution + var osmMapnikUrl='https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'; + var osmMapnikAttrib='Map data © OpenStreetMap contributors'; + var osmMapnik = new L.TileLayer(osmMapnikUrl, {minZoom: 1, maxZoom: 19, attribution: osmMapnikAttrib}); + var osmWikimediaUrl='https://maps.wikimedia.org/osm-intl/{z}/{x}/{y}.png'; + var osmWikimediaAttrib = 'Wikimedia maps beta | Maps data © OpenStreetMap contributors'; + var osmWikimedia = new L.TileLayer(osmWikimediaUrl, {minZoom: 1, maxZoom: 19, attribution: osmWikimediaAttrib}); + // init map view + if(map_bounds) { + // TODO hack: https://github.com/Leaflet/Leaflet/issues/2021 + // Still useful ? + setTimeout(function () { + map.fitBounds(map_bounds, { + maxZoom:17 + }); + }, 0); + } else if (map_lon && map_lat) { + if(map_zoom) { + map.setView(new L.latLng(map_lat, map_lon),map_zoom); + } else { + map.setView(new L.latLng(map_lat, map_lon),8); + } + } + + map.addLayer(osmMapnik); + + var baseLayers = { + "OSM Mapnik": osmMapnik/*, + "OSM Wikimedia": osmWikimedia*/ + }; + + L.control.layers(baseLayers).addTo(map); + + if(map_geojson) { + L.geoJson(map_geojson).addTo(map); + } /*else if(map_bounds) { + L.rectangle(map_bounds, {color: "#ff7800", weight: 3, fill:false}).addTo(map); + }*/ + }); + + // this event occour only once per element + event.preventDefault(); + }); + }); +})(window, document, window.searx); diff --git a/searx/static/themes/simple/js/searx_src/searx_results.js b/searx/static/themes/simple/js/searx_src/searx_results.js new file mode 100644 index 000000000..b13da8391 --- /dev/null +++ b/searx/static/themes/simple/js/searx_src/searx_results.js @@ -0,0 +1,63 @@ +/** +* searx is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* searx is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with searx. If not, see < http://www.gnu.org/licenses/ >. +* +* (C) 2017 by Alexandre Flament, +*/ +(function(w, d, searx) { + 'use strict'; + + searx.ready(function() { + searx.image_thumbnail_layout = new searx.ImageLayout('#urls', '#urls .result-images', 'img.image_thumbnail', 200); + searx.image_thumbnail_layout.watch(); + + searx.on('.btn-collapse', 'click', function(event) { + var btnLabelCollapsed = this.getAttribute('data-btn-text-collapsed'); + var btnLabelNotCollapsed = this.getAttribute('data-btn-text-not-collapsed'); + var target = this.getAttribute('data-target'); + var targetElement = d.querySelector(target); + var html = this.innerHTML; + if (this.classList.contains('collapsed')) { + html = html.replace(btnLabelCollapsed, btnLabelNotCollapsed); + } else { + html = html.replace(btnLabelNotCollapsed, btnLabelCollapsed); + } + this.innerHTML = html; + this.classList.toggle('collapsed'); + targetElement.classList.toggle('invisible'); + }); + + searx.on('.media-loader', 'click', function(event) { + var target = this.getAttribute('data-target'); + var iframe_load = d.querySelector(target + ' > iframe'); + var srctest = iframe_load.getAttribute('src'); + if (srctest === null || srctest === undefined || srctest === false) { + iframe_load.setAttribute('src', iframe_load.getAttribute('data-src')); + } + }); + + w.addEventListener('scroll', function() { + var e = d.getElementById('backToTop'), + scrollTop = document.documentElement.scrollTop || document.body.scrollTop; + if (e !== null) { + if (scrollTop >= 200) { + e.style.opacity = 1; + } else { + e.style.opacity = 0; + } + } + }); + + }); + +})(window, document, window.searx); diff --git a/searx/static/themes/simple/js/searx_src/searx_search.js b/searx/static/themes/simple/js/searx_src/searx_search.js new file mode 100644 index 000000000..1b93f9039 --- /dev/null +++ b/searx/static/themes/simple/js/searx_src/searx_search.js @@ -0,0 +1,94 @@ +/** +* searx is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* searx is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with searx. If not, see < http://www.gnu.org/licenses/ >. +* +* (C) 2017 by Alexandre Flament, +*/ +(function(w, d, searx) { + 'use strict'; + + var firstFocus = true, qinput_id = "q", qinput; + + function placeCursorAtEnd(element) { + if (element.setSelectionRange) { + var len = element.value.length; + element.setSelectionRange(len, len); + } + } + + function submitIfQuery() { + if (qinput.value.length > 0) { + var search = document.getElementById('search'); + setTimeout(search.submit.bind(search), 0); + } + } + + searx.ready(function() { + qinput = d.getElementById(qinput_id); + + function placeCursorAtEndOnce(e) { + if (firstFocus) { + placeCursorAtEnd(qinput); + firstFocus = false; + } else { + // e.preventDefault(); + } + } + + if (qinput !== null) { + // autocompleter + if (searx.autocompleter) { + searx.autocomplete = AutoComplete.call(w, { + Url: "./autocompleter", + EmptyMessage: searx.noItemFound, + HttpMethod: searx.method, + MinChars: 4, + Delay: 300, + }, "#" + qinput_id); + + // hack, see : https://github.com/autocompletejs/autocomplete.js/issues/37 + w.addEventListener('resize', function() { + var event = new CustomEvent("position"); + qinput.dispatchEvent(event); + }); + } + + qinput.addEventListener('focus', placeCursorAtEndOnce, false); + qinput.focus(); + } + + // vanilla js version of search_on_category_select.js + if (qinput !== null && searx.search_on_category_select) { + d.querySelector('.help').className='invisible'; + + searx.on('#categories input', 'change', function(e) { + var i, categories = d.querySelectorAll('#categories input[type="checkbox"]'); + for(i=0; i