From a1132deaa4618f228e82252397247a150081a5f3 Mon Sep 17 00:00:00 2001 From: Markus Heiser Date: Thu, 23 Jan 2025 11:10:40 +0100 Subject: [web-client] simple theme: move sources to client/simple/src Signed-off-by: Markus Heiser --- client/simple/generated/pygments.less | 173 +++ client/simple/src/js/head/00_init.js | 20 + client/simple/src/js/main/00_toolkit.js | 165 +++ client/simple/src/js/main/infinite_scroll.js | 88 ++ client/simple/src/js/main/keyboard.js | 461 ++++++++ client/simple/src/js/main/mapresult.js | 74 ++ client/simple/src/js/main/preferences.js | 53 + client/simple/src/js/main/results.js | 181 +++ client/simple/src/js/main/search.js | 210 ++++ client/simple/src/less/animations.less | 19 + client/simple/src/less/autocomplete.less | 74 ++ client/simple/src/less/code.less | 51 + client/simple/src/less/definitions.less | 304 +++++ client/simple/src/less/detail.less | 253 +++++ client/simple/src/less/embedded.less | 33 + client/simple/src/less/index.less | 51 + client/simple/src/less/info.less | 10 + client/simple/src/less/mixins.less | 40 + client/simple/src/less/new_issue.less | 37 + client/simple/src/less/preferences.less | 233 ++++ client/simple/src/less/result_templates.less | 7 + client/simple/src/less/rss.less | 45 + client/simple/src/less/search.less | 393 +++++++ client/simple/src/less/stats.less | 106 ++ client/simple/src/less/style-center.less | 118 ++ client/simple/src/less/style-ltr.less | 85 ++ client/simple/src/less/style-rtl.less | 159 +++ client/simple/src/less/style.less | 1162 ++++++++++++++++++++ client/simple/src/less/toolkit.less | 646 +++++++++++ client/simple/src/less/toolkit_loader.less | 29 + .../simple/src/svg/information-circle-outline.svg | 6 + client/simple/src/svg/select-dark.svg | 4 + client/simple/src/svg/select-light.svg | 4 + 33 files changed, 5294 insertions(+) create mode 100644 client/simple/generated/pygments.less create mode 100644 client/simple/src/js/head/00_init.js create mode 100644 client/simple/src/js/main/00_toolkit.js create mode 100644 client/simple/src/js/main/infinite_scroll.js create mode 100644 client/simple/src/js/main/keyboard.js create mode 100644 client/simple/src/js/main/mapresult.js create mode 100644 client/simple/src/js/main/preferences.js create mode 100644 client/simple/src/js/main/results.js create mode 100644 client/simple/src/js/main/search.js create mode 100644 client/simple/src/less/animations.less create mode 100644 client/simple/src/less/autocomplete.less create mode 100644 client/simple/src/less/code.less create mode 100644 client/simple/src/less/definitions.less create mode 100644 client/simple/src/less/detail.less create mode 100644 client/simple/src/less/embedded.less create mode 100644 client/simple/src/less/index.less create mode 100644 client/simple/src/less/info.less create mode 100644 client/simple/src/less/mixins.less create mode 100644 client/simple/src/less/new_issue.less create mode 100644 client/simple/src/less/preferences.less create mode 100644 client/simple/src/less/result_templates.less create mode 100644 client/simple/src/less/rss.less create mode 100644 client/simple/src/less/search.less create mode 100644 client/simple/src/less/stats.less create mode 100644 client/simple/src/less/style-center.less create mode 100644 client/simple/src/less/style-ltr.less create mode 100644 client/simple/src/less/style-rtl.less create mode 100644 client/simple/src/less/style.less create mode 100644 client/simple/src/less/toolkit.less create mode 100644 client/simple/src/less/toolkit_loader.less create mode 100644 client/simple/src/svg/information-circle-outline.svg create mode 100644 client/simple/src/svg/select-dark.svg create mode 100644 client/simple/src/svg/select-light.svg (limited to 'client/simple') diff --git a/client/simple/generated/pygments.less b/client/simple/generated/pygments.less new file mode 100644 index 000000000..971409116 --- /dev/null +++ b/client/simple/generated/pygments.less @@ -0,0 +1,173 @@ +/* + this file is generated automatically by searxng_extra/update/update_pygments.py + using pygments version 2.19.1 +*/ + + +.code-highlight { + + pre { line-height: 100%; } + td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } + span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } + td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } + span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } + .hll { background-color: #ffffcc } + .c { color: #3D7B7B; font-style: italic } /* Comment */ + .err { border: 1px solid #F00 } /* Error */ + .k { color: #008000; font-weight: bold } /* Keyword */ + .o { color: #666 } /* Operator */ + .ch { color: #3D7B7B; font-style: italic } /* Comment.Hashbang */ + .cm { color: #3D7B7B; font-style: italic } /* Comment.Multiline */ + .cp { color: #9C6500 } /* Comment.Preproc */ + .cpf { color: #3D7B7B; font-style: italic } /* Comment.PreprocFile */ + .c1 { color: #3D7B7B; font-style: italic } /* Comment.Single */ + .cs { color: #3D7B7B; font-style: italic } /* Comment.Special */ + .gd { color: #A00000 } /* Generic.Deleted */ + .ge { font-style: italic } /* Generic.Emph */ + .ges { font-weight: bold; font-style: italic } /* Generic.EmphStrong */ + .gr { color: #E40000 } /* Generic.Error */ + .gh { color: #000080; font-weight: bold } /* Generic.Heading */ + .gi { color: #008400 } /* Generic.Inserted */ + .go { color: #717171 } /* Generic.Output */ + .gp { color: #000080; font-weight: bold } /* Generic.Prompt */ + .gs { font-weight: bold } /* Generic.Strong */ + .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ + .gt { color: #04D } /* Generic.Traceback */ + .kc { color: #008000; font-weight: bold } /* Keyword.Constant */ + .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */ + .kn { color: #008000; font-weight: bold } /* Keyword.Namespace */ + .kp { color: #008000 } /* Keyword.Pseudo */ + .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */ + .kt { color: #B00040 } /* Keyword.Type */ + .m { color: #666 } /* Literal.Number */ + .s { color: #BA2121 } /* Literal.String */ + .na { color: #687822 } /* Name.Attribute */ + .nb { color: #008000 } /* Name.Builtin */ + .nc { color: #00F; font-weight: bold } /* Name.Class */ + .no { color: #800 } /* Name.Constant */ + .nd { color: #A2F } /* Name.Decorator */ + .ni { color: #717171; font-weight: bold } /* Name.Entity */ + .ne { color: #CB3F38; font-weight: bold } /* Name.Exception */ + .nf { color: #00F } /* Name.Function */ + .nl { color: #767600 } /* Name.Label */ + .nn { color: #00F; font-weight: bold } /* Name.Namespace */ + .nt { color: #008000; font-weight: bold } /* Name.Tag */ + .nv { color: #19177C } /* Name.Variable */ + .ow { color: #A2F; font-weight: bold } /* Operator.Word */ + .w { color: #BBB } /* Text.Whitespace */ + .mb { color: #666 } /* Literal.Number.Bin */ + .mf { color: #666 } /* Literal.Number.Float */ + .mh { color: #666 } /* Literal.Number.Hex */ + .mi { color: #666 } /* Literal.Number.Integer */ + .mo { color: #666 } /* Literal.Number.Oct */ + .sa { color: #BA2121 } /* Literal.String.Affix */ + .sb { color: #BA2121 } /* Literal.String.Backtick */ + .sc { color: #BA2121 } /* Literal.String.Char */ + .dl { color: #BA2121 } /* Literal.String.Delimiter */ + .sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */ + .s2 { color: #BA2121 } /* Literal.String.Double */ + .se { color: #AA5D1F; font-weight: bold } /* Literal.String.Escape */ + .sh { color: #BA2121 } /* Literal.String.Heredoc */ + .si { color: #A45A77; font-weight: bold } /* Literal.String.Interpol */ + .sx { color: #008000 } /* Literal.String.Other */ + .sr { color: #A45A77 } /* Literal.String.Regex */ + .s1 { color: #BA2121 } /* Literal.String.Single */ + .ss { color: #19177C } /* Literal.String.Symbol */ + .bp { color: #008000 } /* Name.Builtin.Pseudo */ + .fm { color: #00F } /* Name.Function.Magic */ + .vc { color: #19177C } /* Name.Variable.Class */ + .vg { color: #19177C } /* Name.Variable.Global */ + .vi { color: #19177C } /* Name.Variable.Instance */ + .vm { color: #19177C } /* Name.Variable.Magic */ + .il { color: #666 } /* Literal.Number.Integer.Long */ +} + +.code-highlight-dark(){ + .code-highlight { + + pre { line-height: 100%; } + td.linenos .normal { color: #3c4354; background-color: transparent; padding-left: 5px; padding-right: 5px; } + span.linenos { color: #3c4354; background-color: transparent; padding-left: 5px; padding-right: 5px; } + td.linenos .special { color: #3c4354; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } + span.linenos.special { color: #3c4354; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } + .hll { background-color: #6e7681 } + .c { color: #7E8AA1 } /* Comment */ + .err { color: #F88F7F } /* Error */ + .esc { color: #D4D2C8 } /* Escape */ + .g { color: #D4D2C8 } /* Generic */ + .k { color: #FFAD66 } /* Keyword */ + .l { color: #D5FF80 } /* Literal */ + .n { color: #D4D2C8 } /* Name */ + .o { color: #FFAD66 } /* Operator */ + .x { color: #D4D2C8 } /* Other */ + .p { color: #D4D2C8 } /* Punctuation */ + .ch { color: #F88F7F; font-style: italic } /* Comment.Hashbang */ + .cm { color: #7E8AA1 } /* Comment.Multiline */ + .cp { color: #FFAD66; font-weight: bold } /* Comment.Preproc */ + .cpf { color: #7E8AA1 } /* Comment.PreprocFile */ + .c1 { color: #7E8AA1 } /* Comment.Single */ + .cs { color: #7E8AA1; font-style: italic } /* Comment.Special */ + .gd { color: #F88F7F; background-color: #3D1E20 } /* Generic.Deleted */ + .ge { color: #D4D2C8; font-style: italic } /* Generic.Emph */ + .ges { color: #D4D2C8 } /* Generic.EmphStrong */ + .gr { color: #F88F7F } /* Generic.Error */ + .gh { color: #D4D2C8 } /* Generic.Heading */ + .gi { color: #6AD4AF; background-color: #19362C } /* Generic.Inserted */ + .go { color: #7E8AA1 } /* Generic.Output */ + .gp { color: #D4D2C8 } /* Generic.Prompt */ + .gs { color: #D4D2C8; font-weight: bold } /* Generic.Strong */ + .gu { color: #D4D2C8 } /* Generic.Subheading */ + .gt { color: #F88F7F } /* Generic.Traceback */ + .kc { color: #FFAD66 } /* Keyword.Constant */ + .kd { color: #FFAD66 } /* Keyword.Declaration */ + .kn { color: #FFAD66 } /* Keyword.Namespace */ + .kp { color: #FFAD66 } /* Keyword.Pseudo */ + .kr { color: #FFAD66 } /* Keyword.Reserved */ + .kt { color: #73D0FF } /* Keyword.Type */ + .ld { color: #D5FF80 } /* Literal.Date */ + .m { color: #DFBFFF } /* Literal.Number */ + .s { color: #D5FF80 } /* Literal.String */ + .na { color: #FFD173 } /* Name.Attribute */ + .nb { color: #FFD173 } /* Name.Builtin */ + .nc { color: #73D0FF } /* Name.Class */ + .no { color: #FFD173 } /* Name.Constant */ + .nd { color: #7E8AA1; font-weight: bold; font-style: italic } /* Name.Decorator */ + .ni { color: #95E6CB } /* Name.Entity */ + .ne { color: #73D0FF } /* Name.Exception */ + .nf { color: #FFD173 } /* Name.Function */ + .nl { color: #D4D2C8 } /* Name.Label */ + .nn { color: #D4D2C8 } /* Name.Namespace */ + .nx { color: #D4D2C8 } /* Name.Other */ + .py { color: #FFD173 } /* Name.Property */ + .nt { color: #5CCFE6 } /* Name.Tag */ + .nv { color: #D4D2C8 } /* Name.Variable */ + .ow { color: #FFAD66 } /* Operator.Word */ + .pm { color: #D4D2C8 } /* Punctuation.Marker */ + .w { color: #D4D2C8 } /* Text.Whitespace */ + .mb { color: #DFBFFF } /* Literal.Number.Bin */ + .mf { color: #DFBFFF } /* Literal.Number.Float */ + .mh { color: #DFBFFF } /* Literal.Number.Hex */ + .mi { color: #DFBFFF } /* Literal.Number.Integer */ + .mo { color: #DFBFFF } /* Literal.Number.Oct */ + .sa { color: #F29E74 } /* Literal.String.Affix */ + .sb { color: #D5FF80 } /* Literal.String.Backtick */ + .sc { color: #D5FF80 } /* Literal.String.Char */ + .dl { color: #D5FF80 } /* Literal.String.Delimiter */ + .sd { color: #7E8AA1 } /* Literal.String.Doc */ + .s2 { color: #D5FF80 } /* Literal.String.Double */ + .se { color: #95E6CB } /* Literal.String.Escape */ + .sh { color: #D5FF80 } /* Literal.String.Heredoc */ + .si { color: #95E6CB } /* Literal.String.Interpol */ + .sx { color: #95E6CB } /* Literal.String.Other */ + .sr { color: #95E6CB } /* Literal.String.Regex */ + .s1 { color: #D5FF80 } /* Literal.String.Single */ + .ss { color: #DFBFFF } /* Literal.String.Symbol */ + .bp { color: #5CCFE6 } /* Name.Builtin.Pseudo */ + .fm { color: #FFD173 } /* Name.Function.Magic */ + .vc { color: #D4D2C8 } /* Name.Variable.Class */ + .vg { color: #D4D2C8 } /* Name.Variable.Global */ + .vi { color: #D4D2C8 } /* Name.Variable.Instance */ + .vm { color: #D4D2C8 } /* Name.Variable.Magic */ + .il { color: #DFBFFF } /* Literal.Number.Integer.Long */ + } +} diff --git a/client/simple/src/js/head/00_init.js b/client/simple/src/js/head/00_init.js new file mode 100644 index 000000000..a7c61c43e --- /dev/null +++ b/client/simple/src/js/head/00_init.js @@ -0,0 +1,20 @@ +/* SPDX-License-Identifier: AGPL-3.0-or-later */ +(function (w, d) { + 'use strict'; + + // add data- properties + var script = d.currentScript || (function () { + var scripts = d.getElementsByTagName('script'); + return scripts[scripts.length - 1]; + })(); + + w.searxng = { + settings: JSON.parse(atob(script.getAttribute('client_settings'))) + }; + + // update the css + var htmlElement = d.getElementsByTagName("html")[0]; + htmlElement.classList.remove('no-js'); + htmlElement.classList.add('js'); + +})(window, document); diff --git a/client/simple/src/js/main/00_toolkit.js b/client/simple/src/js/main/00_toolkit.js new file mode 100644 index 000000000..4e374a019 --- /dev/null +++ b/client/simple/src/js/main/00_toolkit.js @@ -0,0 +1,165 @@ +/** + * @license + * (C) Copyright Contributors to the SearXNG project. + * (C) Copyright Contributors to the searx project (2014 - 2021). + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +window.searxng = (function (w, d) { + + 'use strict'; + + // not invented here toolkit 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); + } + } + + var searxng = window.searxng || {}; + + searxng.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); + } + }; + + searxng.ready = function (callback) { + if (document.readyState != 'loading') { + callback.call(w); + } else { + w.addEventListener('DOMContentLoaded', callback.bind(w)); + } + }; + + searxng.http = function (method, url, data = null) { + return new Promise(function (resolve, reject) { + try { + var req = new XMLHttpRequest(); + req.open(method, url, true); + req.timeout = 20000; + + // 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")); + }; + + req.ontimeout = function () { + reject(Error("Timeout")); + } + + // Make the request + if (data) { + req.send(data) + } else { + req.send(); + } + } catch (ex) { + reject(ex); + } + }); + }; + + searxng.loadStyle = function (src) { + var path = searxng.settings.theme_static_path + "/" + 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); + } + }; + + searxng.loadScript = function (src, callback) { + var path = searxng.settings.theme_static_path + "/" + 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."); + } + }; + + searxng.insertBefore = function (newNode, referenceNode) { + referenceNode.parentNode.insertBefore(newNode, referenceNode); + }; + + searxng.insertAfter = function (newNode, referenceNode) { + referenceNode.parentNode.insertAfter(newNode, referenceNode.nextSibling); + }; + + searxng.on('.close', 'click', function () { + this.parentNode.classList.add('invisible'); + }); + + function getEndpoint () { + for (var className of d.getElementsByTagName('body')[0].classList.values()) { + if (className.endsWith('_endpoint')) { + return className.split('_')[0]; + } + } + return ''; + } + + searxng.endpoint = getEndpoint(); + + return searxng; +})(window, document); diff --git a/client/simple/src/js/main/infinite_scroll.js b/client/simple/src/js/main/infinite_scroll.js new file mode 100644 index 000000000..07db3305a --- /dev/null +++ b/client/simple/src/js/main/infinite_scroll.js @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +/* global searxng */ + +searxng.ready(function () { + 'use strict'; + + searxng.infinite_scroll_supported = ( + 'IntersectionObserver' in window && + 'IntersectionObserverEntry' in window && + 'intersectionRatio' in window.IntersectionObserverEntry.prototype); + + if (searxng.endpoint !== 'results') { + return; + } + + if (!searxng.infinite_scroll_supported) { + console.log('IntersectionObserver not supported'); + return; + } + + let d = document; + var onlyImages = d.getElementById('results').classList.contains('only_template_images'); + + function newLoadSpinner () { + var loader = d.createElement('div'); + loader.classList.add('loader'); + return loader; + } + + function replaceChildrenWith (element, children) { + element.textContent = ''; + children.forEach(child => element.appendChild(child)); + } + + function loadNextPage (callback) { + var form = d.querySelector('#pagination form.next_page'); + if (!form) { + return + } + replaceChildrenWith(d.querySelector('#pagination'), [ newLoadSpinner() ]); + var formData = new FormData(form); + searxng.http('POST', d.querySelector('#search').getAttribute('action'), formData).then( + function (response) { + var nextPageDoc = new DOMParser().parseFromString(response, 'text/html'); + var articleList = nextPageDoc.querySelectorAll('#urls article'); + var paginationElement = nextPageDoc.querySelector('#pagination'); + d.querySelector('#pagination').remove(); + if (articleList.length > 0 && !onlyImages) { + // do not add
element when there are only images + d.querySelector('#urls').appendChild(d.createElement('hr')); + } + articleList.forEach(articleElement => { + d.querySelector('#urls').appendChild(articleElement); + }); + if (paginationElement) { + d.querySelector('#results').appendChild(paginationElement); + callback(); + } + } + ).catch( + function (err) { + console.log(err); + var e = d.createElement('div'); + e.textContent = searxng.settings.translations.error_loading_next_page; + e.classList.add('dialog-error'); + e.setAttribute('role', 'alert'); + replaceChildrenWith(d.querySelector('#pagination'), [ e ]); + } + ) + } + + if (searxng.settings.infinite_scroll && searxng.infinite_scroll_supported) { + const intersectionObserveOptions = { + rootMargin: "20rem", + }; + const observedSelector = 'article.result:last-child'; + const observer = new IntersectionObserver(entries => { + const paginationEntry = entries[0]; + if (paginationEntry.isIntersecting) { + observer.unobserve(paginationEntry.target); + loadNextPage(() => observer.observe(d.querySelector(observedSelector), intersectionObserveOptions)); + } + }); + observer.observe(d.querySelector(observedSelector), intersectionObserveOptions); + } + +}); diff --git a/client/simple/src/js/main/keyboard.js b/client/simple/src/js/main/keyboard.js new file mode 100644 index 000000000..e16134579 --- /dev/null +++ b/client/simple/src/js/main/keyboard.js @@ -0,0 +1,461 @@ +/* SPDX-License-Identifier: AGPL-3.0-or-later */ +/* global searxng */ + +searxng.ready(function () { + + function isElementInDetail (el) { + while (el !== undefined) { + if (el.classList.contains('detail')) { + return true; + } + if (el.classList.contains('result')) { + // we found a result, no need to go to the root of the document: + // el is not inside a
element + return false; + } + el = el.parentNode; + } + return false; + } + + function getResultElement (el) { + while (el !== undefined) { + if (el.classList.contains('result')) { + return el; + } + el = el.parentNode; + } + return undefined; + } + + function isImageResult (resultElement) { + return resultElement && resultElement.classList.contains('result-images'); + } + + searxng.on('.result', 'click', function (e) { + if (!isElementInDetail(e.target)) { + highlightResult(this)(true, true); + let resultElement = getResultElement(e.target); + if (isImageResult(resultElement)) { + e.preventDefault(); + searxng.selectImage(resultElement); + } + } + }); + + searxng.on('.result a', 'focus', function (e) { + if (!isElementInDetail(e.target)) { + let resultElement = getResultElement(e.target); + if (resultElement && resultElement.getAttribute("data-vim-selected") === null) { + highlightResult(resultElement)(true); + } + if (isImageResult(resultElement)) { + searxng.selectImage(resultElement); + } + } + }, true); + + /* common base for layouts */ + var baseKeyBinding = { + 'Escape': { + key: 'ESC', + fun: removeFocus, + des: 'remove focus from the focused input', + cat: 'Control' + }, + 'c': { + key: 'c', + fun: copyURLToClipboard, + des: 'copy url of the selected result to the clipboard', + cat: 'Results' + }, + 'h': { + key: 'h', + fun: toggleHelp, + des: 'toggle help window', + cat: 'Other' + }, + 'i': { + key: 'i', + fun: searchInputFocus, + des: 'focus on the search input', + cat: 'Control' + }, + 'n': { + key: 'n', + fun: GoToNextPage(), + des: 'go to next page', + cat: 'Results' + }, + 'o': { + key: 'o', + fun: openResult(false), + des: 'open search result', + cat: 'Results' + }, + 'p': { + key: 'p', + fun: GoToPreviousPage(), + des: 'go to previous page', + cat: 'Results' + }, + 'r': { + key: 'r', + fun: reloadPage, + des: 'reload page from the server', + cat: 'Control' + }, + 't': { + key: 't', + fun: openResult(true), + des: 'open the result in a new tab', + cat: 'Results' + }, + }; + var keyBindingLayouts = { + + "default": Object.assign( + { /* SearXNG layout */ + 'ArrowLeft': { + key: '←', + fun: highlightResult('up'), + des: 'select previous search result', + cat: 'Results' + }, + 'ArrowRight': { + key: '→', + fun: highlightResult('down'), + des: 'select next search result', + cat: 'Results' + }, + }, baseKeyBinding), + + 'vim': Object.assign( + { /* Vim-like Key Layout. */ + 'b': { + key: 'b', + fun: scrollPage(-window.innerHeight), + des: 'scroll one page up', + cat: 'Navigation' + }, + 'f': { + key: 'f', + fun: scrollPage(window.innerHeight), + des: 'scroll one page down', + cat: 'Navigation' + }, + 'u': { + key: 'u', + fun: scrollPage(-window.innerHeight / 2), + des: 'scroll half a page up', + cat: 'Navigation' + }, + 'd': { + key: 'd', + fun: scrollPage(window.innerHeight / 2), + des: 'scroll half a page down', + cat: 'Navigation' + }, + 'g': { + key: 'g', + fun: scrollPageTo(-document.body.scrollHeight, 'top'), + des: 'scroll to the top of the page', + cat: 'Navigation' + }, + 'v': { + key: 'v', + fun: scrollPageTo(document.body.scrollHeight, 'bottom'), + des: 'scroll to the bottom of the page', + cat: 'Navigation' + }, + 'k': { + key: 'k', + fun: highlightResult('up'), + des: 'select previous search result', + cat: 'Results' + }, + 'j': { + key: 'j', + fun: highlightResult('down'), + des: 'select next search result', + cat: 'Results' + }, + 'y': { + key: 'y', + fun: copyURLToClipboard, + des: 'copy url of the selected result to the clipboard', + cat: 'Results' + }, + }, baseKeyBinding) + } + + var keyBindings = keyBindingLayouts[searxng.settings.hotkeys] || keyBindingLayouts.default; + + searxng.on(document, "keydown", function (e) { + // check for modifiers so we don't break browser's hotkeys + if ( + Object.prototype.hasOwnProperty.call(keyBindings, e.key) + && !e.ctrlKey && !e.altKey + && !e.shiftKey && !e.metaKey + ) { + var tagName = e.target.tagName.toLowerCase(); + if (e.key === 'Escape') { + keyBindings[e.key].fun(e); + } else { + if (e.target === document.body || tagName === 'a' || tagName === 'button') { + e.preventDefault(); + keyBindings[e.key].fun(); + } + } + } + }); + + function highlightResult (which) { + return function (noScroll, keepFocus) { + 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'); + results = Array.from(results); // convert NodeList to Array for further use + + 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 = results[results.indexOf(current) + 1] || current; + break; + case 'up': + next = results[results.indexOf(current) - 1] || current; + 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'); + if (!keepFocus) { + 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 (e) { + const tagName = e.target.tagName.toLowerCase(); + if (document.activeElement && (tagName === 'input' || tagName === 'select' || tagName === 'textarea')) { + document.activeElement.blur(); + } else { + searxng.closeDetail(); + } + } + + function pageButtonClick (css_selector) { + return function () { + var button = document.querySelector(css_selector); + if (button) { + button.click(); + } + }; + } + + function GoToNextPage () { + return pageButtonClick('nav#pagination .next_page button[type="submit"]'); + } + + function GoToPreviousPage () { + return pageButtonClick('nav#pagination .previous_page button[type="submit"]'); + } + + 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); + var q = document.querySelector('#q'); + q.focus(); + if (q.setSelectionRange) { + var len = q.value.length; + q.setSelectionRange(len, len); + } + } + + function openResult (newTab) { + return function () { + var link = document.querySelector('.result[data-vim-selected] h3 a'); + if (link === null) { + link = document.querySelector('.result[data-vim-selected] > a'); + } + if (link !== null) { + var url = link.getAttribute('href'); + if (newTab) { + window.open(url); + } else { + window.location.href = url; + } + } + }; + } + + function initHelpContent (divElement) { + var categories = {}; + + for (var k in keyBindings) { + var key = keyBindings[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 += '

How to navigate SearXNG with hotkeys

'; + html += ''; + + 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 += ''; // col-sm-* + + if (!first || lastCategory) { + html += ''; // row + } + } + + html += '
'; + + html += '

' + cat[0].cat + '

'; + html += '
    '; + + for (var cj in cat) { + html += '
  • ' + cat[cj].key + ' ' + cat[cj].des + '
  • '; + } + + html += '
'; + html += '
'; + + divElement.innerHTML = html; + } + + function toggleHelp () { + var helpPanel = document.querySelector('#vim-hotkeys-help'); + if (helpPanel === undefined || helpPanel === null) { + // first call + helpPanel = document.createElement('div'); + helpPanel.id = 'vim-hotkeys-help'; + helpPanel.className = 'dialog-modal'; + initHelpContent(helpPanel); + var body = document.getElementsByTagName('body')[0]; + body.appendChild(helpPanel); + } else { + // toggle hidden + helpPanel.classList.toggle('invisible'); + return; + } + } + + function copyURLToClipboard () { + var currentUrlElement = document.querySelector('.result[data-vim-selected] h3 a'); + if (currentUrlElement === null) return; + + const url = currentUrlElement.getAttribute('href'); + navigator.clipboard.writeText(url); + } + + searxng.scrollPageToSelected = scrollPageToSelected; + searxng.selectNext = highlightResult('down'); + searxng.selectPrevious = highlightResult('up'); +}); diff --git a/client/simple/src/js/main/mapresult.js b/client/simple/src/js/main/mapresult.js new file mode 100644 index 000000000..2c3777678 --- /dev/null +++ b/client/simple/src/js/main/mapresult.js @@ -0,0 +1,74 @@ +/* SPDX-License-Identifier: AGPL-3.0-or-later */ +/* global L */ +(function (w, d, searxng) { + 'use strict'; + + searxng.ready(function () { + searxng.on('.searxng_init_map', 'click', function (event) { + // no more request + this.classList.remove("searxng_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); + + searxng.loadStyle('css/leaflet.css'); + searxng.loadScript('js/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 | 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 occur only once per element + event.preventDefault(); + }); + }); +})(window, document, window.searxng); diff --git a/client/simple/src/js/main/preferences.js b/client/simple/src/js/main/preferences.js new file mode 100644 index 000000000..a0b853d61 --- /dev/null +++ b/client/simple/src/js/main/preferences.js @@ -0,0 +1,53 @@ +/* SPDX-License-Identifier: AGPL-3.0-or-later */ +(function (w, d, searxng) { + 'use strict'; + + if (searxng.endpoint !== 'preferences') { + return; + } + + searxng.ready(function () { + let engine_descriptions = null; + function load_engine_descriptions () { + if (engine_descriptions == null) { + searxng.http("GET", "engine_descriptions.json").then(function (content) { + engine_descriptions = JSON.parse(content); + for (const [engine_name, description] of Object.entries(engine_descriptions)) { + let elements = d.querySelectorAll('[data-engine-name="' + engine_name + '"] .engine-description'); + for (const element of elements) { + let source = ' (' + searxng.settings.translations.Source + ': ' + description[1] + ')'; + element.innerHTML = description[0] + source; + } + } + }); + } + } + + for (const el of d.querySelectorAll('[data-engine-name]')) { + searxng.on(el, 'mouseenter', load_engine_descriptions); + } + + const enableAllEngines = d.querySelectorAll(".enable-all-engines"); + const disableAllEngines = d.querySelectorAll(".disable-all-engines"); + const engineToggles = d.querySelectorAll('tbody input[type=checkbox][class~=checkbox-onoff]'); + const toggleEngines = (enable) => { + for (const el of engineToggles) { + // check if element visible, so that only engines of the current category are modified + if (el.offsetParent !== null) el.checked = !enable; + } + }; + for (const el of enableAllEngines) { + searxng.on(el, 'click', () => toggleEngines(true)); + } + for (const el of disableAllEngines) { + searxng.on(el, 'click', () => toggleEngines(false)); + } + + const copyHashButton = d.querySelector("#copy-hash"); + searxng.on(copyHashButton, 'click', (e) => { + e.preventDefault(); + navigator.clipboard.writeText(copyHashButton.dataset.hash); + copyHashButton.innerText = copyHashButton.dataset.copiedText; + }); + }); +})(window, document, window.searxng); diff --git a/client/simple/src/js/main/results.js b/client/simple/src/js/main/results.js new file mode 100644 index 000000000..cc7c7efcd --- /dev/null +++ b/client/simple/src/js/main/results.js @@ -0,0 +1,181 @@ +/* SPDX-License-Identifier: AGPL-3.0-or-later */ +(function (w, d, searxng) { + 'use strict'; + + if (searxng.endpoint !== 'results') { + return; + } + + searxng.ready(function () { + d.querySelectorAll('#urls img').forEach( + img => + img.addEventListener( + 'error', () => { + // console.log("ERROR can't load: " + img.src); + img.src = window.searxng.settings.theme_static_path + "/img/img_load_error.svg"; + }, + {once: true} + )); + + if (d.querySelector('#search_url button#copy_url')) { + d.querySelector('#search_url button#copy_url').style.display = "block"; + } + + searxng.on('.btn-collapse', 'click', function () { + 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'); + }); + + searxng.on('.media-loader', 'click', function () { + 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')); + } + }); + + searxng.on('#copy_url', 'click', function () { + var target = this.parentElement.querySelector('pre'); + navigator.clipboard.writeText(target.innerText); + this.innerText = this.dataset.copiedText; + }); + + // searxng.selectImage (gallery) + // ----------------------------- + + // setTimeout() ID, needed to cancel *last* loadImage + let imgTimeoutID; + + // progress spinner, while an image is loading + const imgLoaderSpinner = d.createElement('div'); + imgLoaderSpinner.classList.add('loader'); + + // singleton image object, which is used for all loading processes of a + // detailed image + const imgLoader = new Image(); + + const loadImage = (imgSrc, onSuccess) => { + // if defered image load exists, stop defered task. + if (imgTimeoutID) clearTimeout(imgTimeoutID); + + // defer load of the detail image for 1 sec + imgTimeoutID = setTimeout(() => { + imgLoader.src = imgSrc; + }, 1000); + + // set handlers in the on-properties + imgLoader.onload = () => { + onSuccess(); + imgLoaderSpinner.remove(); + }; + imgLoader.onerror = () => { + imgLoaderSpinner.remove(); + }; + }; + + searxng.selectImage = (resultElement) => { + + // add a class that can be evaluated in the CSS and indicates that the + // detail view is open + d.getElementById('results').classList.add('image-detail-open'); + + // add a hash to the browser history so that pressing back doesn't return + // to the previous page this allows us to dismiss the image details on + // pressing the back button on mobile devices + window.location.hash = '#image-viewer'; + + searxng.scrollPageToSelected(); + + // if there is none element given by the caller, stop here + if (!resultElement) return; + + // find object in the element, if there is none, stop here. + const img = resultElement.querySelector('.result-images-source img'); + if (!img) return; + + // + const src = img.getAttribute('data-src'); + + // already loaded high-res image or no high-res image available + if (!src) return; + + // use the image thumbnail until the image is fully loaded + const thumbnail = resultElement.querySelector('.image_thumbnail'); + img.src = thumbnail.src; + + // show a progress spinner + const detailElement = resultElement.querySelector('.detail'); + detailElement.appendChild(imgLoaderSpinner); + + // load full size image in background + loadImage(src, () => { + // after the singelton loadImage has loaded the detail image into the + // cache, it can be used in the origin as src property. + img.src = src; + img.removeAttribute('data-src'); + }); + }; + + searxng.closeDetail = function () { + d.getElementById('results').classList.remove('image-detail-open'); + // remove #image-viewer hash from url by navigating back + if (window.location.hash == '#image-viewer') window.history.back(); + searxng.scrollPageToSelected(); + }; + searxng.on('.result-detail-close', 'click', e => { + e.preventDefault(); + searxng.closeDetail(); + }); + searxng.on('.result-detail-previous', 'click', e => { + e.preventDefault(); + searxng.selectPrevious(false); + }); + searxng.on('.result-detail-next', 'click', e => { + e.preventDefault(); + searxng.selectNext(false); + }); + + // listen for the back button to be pressed and dismiss the image details when called + window.addEventListener('hashchange', () => { + if (window.location.hash != '#image-viewer') searxng.closeDetail(); + }); + + d.querySelectorAll('.swipe-horizontal').forEach( + obj => { + obj.addEventListener('swiped-left', function () { + searxng.selectNext(false); + }); + obj.addEventListener('swiped-right', function () { + searxng.selectPrevious(false); + }); + } + ); + + w.addEventListener('scroll', function () { + var e = d.getElementById('backToTop'), + scrollTop = document.documentElement.scrollTop || document.body.scrollTop, + results = d.getElementById('results'); + if (e !== null) { + if (scrollTop >= 100) { + results.classList.add('scrolling'); + } else { + results.classList.remove('scrolling'); + } + } + }, true); + + }); + +})(window, document, window.searxng); diff --git a/client/simple/src/js/main/search.js b/client/simple/src/js/main/search.js new file mode 100644 index 000000000..aca146c41 --- /dev/null +++ b/client/simple/src/js/main/search.js @@ -0,0 +1,210 @@ +/* SPDX-License-Identifier: AGPL-3.0-or-later */ +/* exported AutoComplete */ + +import AutoComplete from "../../../node_modules/autocomplete-js/dist/autocomplete.js"; + +(function (w, d, searxng) { + 'use strict'; + + var qinput_id = "q", qinput; + + const isMobile = window.matchMedia("only screen and (max-width: 50em)").matches; + + function submitIfQuery () { + if (qinput.value.length > 0) { + var search = document.getElementById('search'); + setTimeout(search.submit.bind(search), 0); + } + } + + function createClearButton (qinput) { + var cs = document.getElementById('clear_search'); + var updateClearButton = function () { + if (qinput.value.length === 0) { + cs.classList.add("empty"); + } else { + cs.classList.remove("empty"); + } + }; + + // update status, event listener + updateClearButton(); + cs.addEventListener('click', function (ev) { + qinput.value = ''; + qinput.focus(); + updateClearButton(); + ev.preventDefault(); + }); + qinput.addEventListener('input', updateClearButton, false); + } + + searxng.ready(function () { + qinput = d.getElementById(qinput_id); + + if (qinput !== null) { + // clear button + createClearButton(qinput); + + // autocompleter + if (searxng.settings.autocomplete) { + searxng.autocomplete = AutoComplete.call(w, { + Url: "./autocompleter", + EmptyMessage: searxng.settings.translations.no_item_found, + HttpMethod: searxng.settings.method, + HttpHeaders: { + "Content-type": "application/x-www-form-urlencoded", + "X-Requested-With": "XMLHttpRequest" + }, + MinChars: searxng.settings.autocomplete_min, + Delay: 300, + _Position: function () {}, + _Open: function () { + var params = this; + Array.prototype.forEach.call(this.DOMResults.getElementsByTagName("li"), function (li) { + if (li.getAttribute("class") != "locked") { + li.onmousedown = function () { + params._Select(li); + }; + } + }); + }, + _Select: function (item) { + AutoComplete.defaults._Select.call(this, item); + var form = item.closest('form'); + if (form) { + form.submit(); + } + }, + _MinChars: function () { + if (this.Input.value.indexOf('!') > -1) { + return 0; + } else { + return AutoComplete.defaults._MinChars.call(this); + } + }, + KeyboardMappings: Object.assign({}, AutoComplete.defaults.KeyboardMappings, { + "KeyUpAndDown_up": Object.assign({}, AutoComplete.defaults.KeyboardMappings.KeyUpAndDown_up, { + Callback: function (event) { + AutoComplete.defaults.KeyboardMappings.KeyUpAndDown_up.Callback.call(this, event); + var liActive = this.DOMResults.querySelector("li.active"); + if (liActive) { + AutoComplete.defaults._Select.call(this, liActive); + } + }, + }), + "Tab": Object.assign({}, AutoComplete.defaults.KeyboardMappings.Enter, { + Conditions: [{ + Is: 9, + Not: false + }], + Callback: function (event) { + if (this.DOMResults.getAttribute("class").indexOf("open") != -1) { + var liActive = this.DOMResults.querySelector("li.active"); + if (liActive !== null) { + AutoComplete.defaults._Select.call(this, liActive); + event.preventDefault(); + } + } + }, + }) + }), + }, "#" + qinput_id); + } + + /* + Monkey patch autocomplete.js to fix a bug + With the POST method, the values are not URL encoded: query like "1 + 1" are sent as "1 1" since space are URL encoded as plus. + See HTML specifications: + * HTML5: https://url.spec.whatwg.org/#concept-urlencoded-serializer + * HTML4: https://www.w3.org/TR/html401/interact/forms.html#h-17.13.4.1 + + autocomplete.js does not URL encode the name and values: + https://github.com/autocompletejs/autocomplete.js/blob/87069524f3b95e68f1b54d8976868e0eac1b2c83/src/autocomplete.ts#L665 + + The monkey patch overrides the compiled version of the ajax function. + See https://github.com/autocompletejs/autocomplete.js/blob/87069524f3b95e68f1b54d8976868e0eac1b2c83/dist/autocomplete.js#L143-L158 + The patch changes only the line 156 from + params.Request.send(params._QueryArg() + "=" + params._Pre()); + to + params.Request.send(encodeURIComponent(params._QueryArg()) + "=" + encodeURIComponent(params._Pre())); + + Related to: + * https://github.com/autocompletejs/autocomplete.js/issues/78 + * https://github.com/searxng/searxng/issues/1695 + */ + 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(encodeURIComponent(params._QueryArg()) + "=" + encodeURIComponent(params._Pre())); + } + }; + + if (!isMobile && document.querySelector('.index_endpoint')) { + qinput.focus(); + } + } + + // Additionally to searching when selecting a new category, we also + // automatically start a new search request when the user changes a search + // filter (safesearch, time range or language) (this requires JavaScript + // though) + if ( + qinput !== null + && searxng.settings.search_on_category_select + // If .search_filters is undefined (invisible) we are on the homepage and + // hence don't have to set any listeners + && d.querySelector(".search_filters") != null + ) { + searxng.on(d.getElementById('safesearch'), 'change', submitIfQuery); + searxng.on(d.getElementById('time_range'), 'change', submitIfQuery); + searxng.on(d.getElementById('language'), 'change', submitIfQuery); + } + + const categoryButtons = d.querySelectorAll("button.category_button"); + for (let button of categoryButtons) { + searxng.on(button, 'click', (event) => { + if (event.shiftKey) { + event.preventDefault(); + button.classList.toggle("selected"); + return; + } + + // manually deselect the old selection when a new category is selected + const selectedCategories = d.querySelectorAll("button.category_button.selected"); + for (let categoryButton of selectedCategories) { + categoryButton.classList.remove("selected"); + } + button.classList.add("selected"); + }) + } + + // override form submit action to update the actually selected categories + const form = d.querySelector("#search"); + if (form != null) { + searxng.on(form, 'submit', (event) => { + event.preventDefault(); + const categoryValuesInput = d.querySelector("#selected-categories"); + if (categoryValuesInput) { + let categoryValues = []; + for (let categoryButton of categoryButtons) { + if (categoryButton.classList.contains("selected")) { + categoryValues.push(categoryButton.name.replace("category_", "")); + } + } + categoryValuesInput.value = categoryValues.join(","); + } + form.submit(); + }); + } + }); + +})(window, document, window.searxng); diff --git a/client/simple/src/less/animations.less b/client/simple/src/less/animations.less new file mode 100644 index 000000000..75c98308f --- /dev/null +++ b/client/simple/src/less/animations.less @@ -0,0 +1,19 @@ +.dialog-modal { + animation-name: dialogmodal; + animation-duration: 0.13s; + + @keyframes dialogmodal { + 0% { + opacity: 0; + } + + 50% { + opacity: 0.5; + transform: translate(-50%, -50%) scale(1.05); + } + } +} + +input.checkbox-onoff[type="checkbox"]::before { + transition: left 0.25s; +} diff --git a/client/simple/src/less/autocomplete.less b/client/simple/src/less/autocomplete.less new file mode 100644 index 000000000..8285ff2c6 --- /dev/null +++ b/client/simple/src/less/autocomplete.less @@ -0,0 +1,74 @@ +/*! Autocomplete.js v2.6.3 | license MIT | (c) 2017, Baptiste Donaux | http://autocomplete-js.com */ + +.autocomplete { + position: absolute; + width: @search-width; + max-height: 0; + overflow-y: hidden; + .ltr-text-align-left(); + + .rounded-corners; + + &:active, + &:focus, + &:hover { + background-color: var(--color-autocomplete-background); + } + + &:empty { + display: none; + } + + > ul { + list-style-type: none; + margin: 0; + padding: 0; + + > li { + cursor: pointer; + padding: 0.5rem 1rem; + + &.active, + &:active, + &:focus, + &:hover { + background-color: var(--color-autocomplete-background-hover); + + a:active, + a:focus, + a:hover { + text-decoration: none; + } + } + + &.locked { + cursor: inherit; + } + } + } + + &.open { + display: block; + background-color: var(--color-autocomplete-background); + color: var(--color-autocomplete-font); + max-height: 32rem; + overflow-y: auto; + z-index: 5000; + margin-top: 3.5rem; + border-radius: 0.8rem; + + &:empty { + display: none; + } + } +} + +@media screen and (max-width: @phone) { + .autocomplete { + width: 100%; + + > ul > li { + padding: 1rem; + } + } +} diff --git a/client/simple/src/less/code.less b/client/simple/src/less/code.less new file mode 100644 index 000000000..dd0998b23 --- /dev/null +++ b/client/simple/src/less/code.less @@ -0,0 +1,51 @@ +@import "../../generated/pygments.less"; + +.codelines { + margin: @results-margin 0 0 0; + padding: @result-padding 0 0 0; +} + +.code-highlight-sxng() { + .code-highlight { + pre { + overflow: auto; + margin: 0; + padding: 0 0 0.75rem 0; + } + + .linenos { + user-select: none; + cursor: default; + + &::selection { + background: transparent; /* WebKit/Blink Browsers */ + } + + &::-moz-selection { + background: transparent; /* Gecko Browsers */ + } + margin-right: 8px; + text-align: right; + } + + span.linenos { + color: var(--color-line-number); + } + } +} + +.code-highlight-sxng(); + +/// Dark Theme (autoswitch based on device pref) +@media (prefers-color-scheme: dark) { + :root.theme-auto { + .code-highlight-dark(); + .code-highlight-sxng(); + } +} + +// Dark Theme by preferences +:root.theme-dark { + .code-highlight-dark(); + .code-highlight-sxng(); +} diff --git a/client/simple/src/less/definitions.less b/client/simple/src/less/definitions.less new file mode 100644 index 000000000..7501305fc --- /dev/null +++ b/client/simple/src/less/definitions.less @@ -0,0 +1,304 @@ +/* + * SearXNG, A privacy-respecting, hackable metasearch engine + * + * To change the colors of the site, simple edit this variables + */ + +/// Light Theme +:root { + /// Base Colors + --color-base-font: #444; + --color-base-font-rgb: 68, 68, 68; + --color-base-background: #fff; + --color-base-background-mobile: #f2f5f8; + --color-url-font: #334999; + --color-url-visited-font: #9822c3; + /// Header Colors + --color-header-background: #fdfbff; + --color-header-border: #ddd; + /// Footer Colors + --color-footer-background: #fdfbff; + --color-footer-border: #ddd; + /// Sidebar Colors + --color-sidebar-border: #ddd; + --color-sidebar-font: #000; + --color-sidebar-background: #fff; + /// BackToTop Colors + --color-backtotop-font: #444; + --color-backtotop-border: #ddd; + --color-backtotop-background: #fff; + /// Button Colors + --color-btn-background: #3050ff; + --color-btn-font: #fff; + --color-show-btn-background: #bbb; + --color-show-btn-font: #000; + /// Search Input Colors + --color-search-border: #bbb; + --color-search-shadow: 0 2px 8px rgb(34 38 46 / 25%); + --color-search-background: #fff; + --color-search-font: #222; + --color-search-background-hover: #3050ff; + /// Modal Colors + --color-error: #db3434; + --color-error-background: lighten(#db3434, 40%); + --color-warning: #dbba34; + --color-warning-background: lighten(#dbba34, 40%); + --color-success: #42db34; + --color-success-background: lighten(#42db34, 40%); + /// Categories Colors + --color-categories-item-selected-font: #3050ff; + --color-categories-item-border-selected: #3050ff; + /// Autocomplete Colors + --color-autocomplete-font: #000; + --color-autocomplete-border: #bbb; + --color-autocomplete-shadow: 0 2px 8px rgb(34 38 46 / 25%); + --color-autocomplete-background: #fff; + --color-autocomplete-background-hover: #e3e3e3; + /// Answer Colors + --color-answer-font: #444; // same as --color-base-font + --color-answer-background: #fff; + /// Results Colors + --color-result-background: #fff; + --color-result-border: #ddd; + --color-result-url-font: #000; + --color-result-vim-selected: #f7f7f7; + --color-result-vim-arrow: #000bbb; + --color-result-description-highlight-font: #000; + --color-result-link-font: #000bbb; + --color-result-link-font-highlight: #000bbb; + --color-result-link-visited-font: #9822c3; + --color-result-publishdate-font: #777; + --color-result-engines-font: #545454; + --color-result-search-url-border: #ddd; + --color-result-search-url-font: #000; + // Images Colors + --color-result-image-span-font: #444; + --color-result-image-span-font-selected: #fff; + --color-result-image-background: #fff; + /// Settings Colors + --color-settings-tr-hover: #ebebeb; + --color-settings-engine-description-font: #545454; + --color-settings-table-group-background: #0001; + /// Detail modal + --color-result-detail-font: #fff; + --color-result-detail-label-font: lightgray; + --color-result-detail-background: #242424; + --color-result-detail-hr: #555; + --color-result-detail-link: #8af; + --color-result-detail-loader-border: rgb(255 255 255 / 20%); + --color-result-detail-loader-borderleft: rgb(0 0 0 / 0%); + /// Toolkit Colors + --color-toolkit-badge-font: #fff; + --color-toolkit-badge-background: #545454; + --color-toolkit-kbd-font: #fff; + --color-toolkit-kbd-background: #000; + --color-toolkit-dialog-border: #ddd; + --color-toolkit-dialog-background: #fff; + --color-toolkit-tabs-label-border: #fff; + --color-toolkit-tabs-section-border: #ddd; + --color-toolkit-select-background: #e1e1e1; + --color-toolkit-select-border: #ddd; + --color-toolkit-select-background-hover: #bbb; + --color-toolkit-input-text-font: #222; + --color-toolkit-checkbox-onoff-off-background: #ddd; + --color-toolkit-checkbox-onoff-on-background: #ddd; + --color-toolkit-checkbox-onoff-on-mark-background: #3050ff; + --color-toolkit-checkbox-onoff-on-mark-color: #fff; + --color-toolkit-checkbox-onoff-off-mark-background: #aaa; + --color-toolkit-checkbox-onoff-off-mark-color: #fff; + --color-toolkit-checkbox-label-background: #ddd; + --color-toolkit-checkbox-label-border: #ddd; + --color-toolkit-checkbox-input-border: #3050ff; + --color-toolkit-engine-tooltip-border: #ddd; + --color-toolkit-engine-tooltip-background: #fff; + --color-toolkit-loader-border: rgb(0 0 0 / 20%); + --color-toolkit-loader-borderleft: rgb(255 255 255 / 0%); + --color-doc-code: #003; + --color-doc-code-background: #ddeaff; + /// Other misc colors + --color-bar-chart-primary: #5bc0de; + --color-bar-chart-secondary: #deb15b; + --color-image-resolution-background: rgb(0 0 0 / 50%); + --color-image-resolution-font: #fff; + --color-loading-indicator: rgb(255 255 255 / 20%); + --color-loading-indicator-gap: #fff; + --color-line-number: #64708d; + // Favicons Colors + --color-favicon-background-color: #ddd; + --color-favicon-border-color: #ccc; +} + +.dark-themes() { + /// Base Colors + --color-base-font: #bbb; + --color-base-font-rgb: 187, 187, 187; + --color-base-background: #222428; + --color-base-background-mobile: #222428; + --color-url-font: #8af; + --color-url-visited-font: #c09cd9; + /// Header Colors + --color-header-background: #1e1e22; + --color-header-border: #333; + /// Footer Colors + --color-footer-background: #1e1e22; + --color-footer-border: #333; + /// Sidebar Colors + --color-sidebar-border: #555; + --color-sidebar-font: #fff; + --color-sidebar-background: #292c34; + /// BackToTop Colors + --color-backtotop-font: #bbb; + --color-backtotop-border: #333; + --color-backtotop-background: #2b2e36; + /// Button Colors + --color-btn-background: #58f; + --color-btn-font: #222; + --color-show-btn-background: #555; + --color-show-btn-font: #fff; + /// Search Input Colors + --color-search-border: #555; + --color-search-shadow: 0 2px 8px rgb(34 38 46 / 25%); + --color-search-background: #2b2e36; + --color-search-font: #fff; + --color-search-background-hover: #58f; + /// Modal Colors + --color-error: #f55b5b; + --color-error-background: darken(#db3434, 40%); + --color-warning: #f1d561; + --color-warning-background: darken(#dbba34, 40%); + --color-success: #79f56e; + --color-success-background: darken(#42db34, 40%); + /// Categories Colors + --color-categories-item-selected-font: #58f; + --color-categories-item-border-selected: #58f; + /// Autocomplete Colors + --color-autocomplete-font: #fff; + --color-autocomplete-border: #555; + --color-autocomplete-shadow: 0 2px 8px rgb(34 38 46 / 25%); + --color-autocomplete-background: #2b2e36; + --color-autocomplete-background-hover: #1e1e22; + /// Answer Colors + --color-answer-font: #bbb; // same as --color-base-font + --color-answer-background: #26292f; + /// Results Colors + --color-result-background: #26292f; + --color-result-border: #333; + --color-result-url-font: #fff; + --color-result-vim-selected: #1f1f23cc; + --color-result-vim-arrow: #8af; + --color-result-description-highlight-font: #fff; + --color-result-link-font: #8af; + --color-result-link-font-highlight: #8af; + --color-result-link-visited-font: #c09cd9; + --color-result-publishdate-font: #888; + --color-result-engines-font: #a4a4a4; + --color-result-search-url-border: #555; + --color-result-search-url-font: #fff; + /// Detail modal : same as the light version + --color-result-detail-font: #fff; + --color-result-detail-label-font: lightgray; + --color-result-detail-background: #1a1a1c; + --color-result-detail-hr: #555; + --color-result-detail-link: #8af; + --color-result-detail-loader-border: rgb(255 255 255 / 20%); + --color-result-detail-loader-borderleft: rgb(0 0 0 / 0%); + // Images Colors + --color-result-image-span-font: #bbb; + --color-result-image-span-font-selected: #222; + --color-result-image-background: #222; + /// Settings Colors + --color-settings-tr-hover: #2c2c32; + --color-settings-engine-description-font: darken(#dcdcdc, 30%); + --color-settings-table-group-background: #1b1b21; + /// Toolkit Colors + --color-toolkit-badge-font: #fff; + --color-toolkit-badge-background: #555; + --color-toolkit-kbd-font: #000; + --color-toolkit-kbd-background: #fff; + --color-toolkit-dialog-border: #555; + --color-toolkit-dialog-background: #1e1e22; + --color-toolkit-tabs-label-border: #222; + --color-toolkit-tabs-section-border: #555; + --color-toolkit-select-background: #313338; + --color-toolkit-select-border: #555; + --color-toolkit-select-background-hover: #373b49; + --color-toolkit-input-text-font: #fff; + --color-toolkit-checkbox-onoff-off-background: #313338; + --color-toolkit-checkbox-onoff-on-background: #313338; + --color-toolkit-checkbox-onoff-on-mark-background: #58f; + --color-toolkit-checkbox-onoff-on-mark-color: #222; + --color-toolkit-checkbox-onoff-off-mark-background: #ddd; + --color-toolkit-checkbox-onoff-off-mark-color: #222; + --color-toolkit-checkbox-label-background: #222; + --color-toolkit-checkbox-label-border: #333; + --color-toolkit-checkbox-input-border: #58f; + --color-toolkit-engine-tooltip-border: #333; + --color-toolkit-engine-tooltip-background: #222; + --color-toolkit-loader-border: rgb(255 255 255 / 20%); + --color-toolkit-loader-borderleft: rgb(0 0 0 / 0%); + --color-doc-code: #ddd; + --color-doc-code-background: #4d5a6f; + // Favicons Colors + --color-favicon-background-color: #ddd; + --color-favicon-border-color: #ccc; +} + +.black-themes() { + --color-base-background: #000; + --color-base-background-mobile: #000; + --color-header-background: #000; + --color-footer-background: #000; + --color-sidebar-background: #000; +} + +/// Dark Theme (autoswitch based on device pref) +@media (prefers-color-scheme: dark) { + :root.theme-auto { + .dark-themes(); + } +} + +// Dark Theme by preferences +:root.theme-dark { + .dark-themes(); +} + +:root.theme-black { + .dark-themes(); + .black-themes(); +} + +/// General Size +@results-width: 45rem; +@results-sidebar-width: 25rem; +@results-offset: 10rem; +@results-tablet-offset: 0.5rem; +@results-gap: 5rem; +@results-margin: 0.125rem; +@result-padding: 1rem; +@results-image-row-height: 12rem; +@results-image-row-height-phone: 10rem; +@search-width: 44rem; +// heigh of #search, see detail.less +@search-height: 13rem; + +/// Device Size +/// @desktop > @tablet +@tablet: 79.75em; // see https://github.com/searxng/searxng/issues/874 +@phone: 50em; +@small-phone: 35em; +@ultra-small-phone: 20rem; + +/// From style.less +@stacked-bar-chart: rgb(0, 0, 0); + +/// Load fonts from this directory. +@icon-font-path: "../../../fonts/"; +//** File name for all font files. +@icon-font-name: "glyphicons-halflings-regular"; +//** Element ID within SVG icon file. +@icon-font-svg-id: "glyphicons_halflingsregular"; + +// decoration of the select HTML elements +@select-light-svg-path: "../svg/select-light.svg"; +@select-dark-svg-path: "../svg/select-dark.svg"; diff --git a/client/simple/src/less/detail.less b/client/simple/src/less/detail.less new file mode 100644 index 000000000..fd5cd8e05 --- /dev/null +++ b/client/simple/src/less/detail.less @@ -0,0 +1,253 @@ +#main_results #results.image-detail-open.only_template_images { + width: min(98%, 59.25rem) !important; +} + +#main_results #results.only_template_images.image-detail-open #backToTop { + .ltr-left(56.75rem) !important; + .ltr-right(inherit); +} + +article.result-images .detail { + display: none; +} + +#results.image-detail-open article.result-images[data-vim-selected] .detail { + display: flex; + flex-direction: column; + position: fixed; + .ltr-left(60rem); + .ltr-right(0); + top: @search-height; + transition: top 0.064s ease-in 0s; + bottom: 0; + background: var(--color-result-detail-background); + border: 1px solid var(--color-result-detail-background); + z-index: 1000; + padding: 4rem 3rem 3rem 3rem; + + a.result-images-source { + display: block; + flex: 1; + text-align: left; + width: 100%; + border: none; + text-decoration: none; + + img { + padding: 0; + margin: 0; + border: none; + object-fit: contain; + width: inherit; + height: inherit; + max-width: 100%; + min-height: inherit; + max-height: calc(100vh - 25rem - 7rem); + background: inherit; + } + } + + .result-images-labels { + color: var(--color-result-detail-font); + height: 19rem; + + hr { + border-top: 1px solid var(--color-result-detail-hr); + border-bottom: none; + } + + h4 { + height: 2rem; + overflow: hidden; + text-overflow: ellipsis; + font-size: 0.9rem; + margin-bottom: 0; + } + + p { + color: var(--color-result-detail-label-font); + font-size: 0.9rem; + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; + margin: 0.8rem 0; + + span { + display: inline-block; + width: 12rem; + } + } + + h4, + p, + a { + .ltr-text-align-left(); + } + + p.result-content { + height: 2rem; + line-height: unset; + overflow: hidden; + text-overflow: ellipsis; + } + + p.result-url { + white-space: nowrap; + overflow: hidden hidden; + text-overflow: ellipsis; + } + + p.result-content:hover, + p.result-url:hover { + position: relative; + overflow: inherit !important; + background: var(--color-result-detail-background); + text-overflow: inherit !important; + } + + a, + a:visited, + a:hover, + a:active { + color: var(--color-result-detail-link); + } + + a:hover { + text-decoration: underline; + } + } + + a.result-detail-close { + top: 1rem; + .ltr-left(1rem); + padding: 0.4rem; + } + + a.result-detail-previous { + top: 1rem; + .ltr-right(6rem); + // center the icon by moving it slightly on the left + padding-top: 0.4rem; + .ltr-padding-right(0.5rem); + padding-bottom: 0.4rem; + .ltr-padding-left(0.3rem); + } + + a.result-detail-next { + top: 1rem; + .ltr-right(2rem); + padding: 0.4rem; + } + + a.result-detail-close, + a.result-detail-next, + a.result-detail-previous { + border-radius: 50%; + display: block; + width: 1.5rem; + height: 1.5rem; + position: absolute; + filter: opacity(40%); + z-index: 1200; + + span { + display: block; + width: 1.5rem; + height: 1.5rem; + text-align: center; + } + } + + a.result-detail-next, + a.result-detail-previous { + span::before { + // vertical center small icons + vertical-align: sub; + } + } + + a.result-detail-close, + a.result-detail-close:visited, + a.result-detail-close:hover, + a.result-detail-close:active, + a.result-detail-previous, + a.result-detail-previous:visited, + a.result-detail-previous:hover, + a.result-detail-previous:active, + a.result-detail-next, + a.result-detail-next:visited, + a.result-detail-next:hover, + a.result-detail-next:active { + color: var(--color-result-detail-font); + background: var(--color-result-detail-background); + border: 1px solid var(--color-result-detail-font); + } + + a.result-detail-close:focus, + a.result-detail-close:hover, + a.result-detail-previous:focus, + a.result-detail-previous:hover, + a.result-detail-next:focus, + a.result-detail-next:hover { + filter: opacity(80%); + } + + .loader { + position: absolute; + top: 1rem; + .ltr-right(50%); + border-top: 0.5em solid var(--color-result-detail-loader-border); + border-right: 0.5em solid var(--color-result-detail-loader-border); + border-bottom: 0.5em solid var(--color-result-detail-loader-border); + border-left: 0.5em solid var(--color-result-detail-loader-borderleft); + } +} + +#results.image-detail-open.scrolling article.result-images[data-vim-selected] .detail { + top: 0; + + a.result-images-source img { + max-height: calc(100vh - 25rem); + } +} + +@media screen and (max-width: @tablet) { + #results.image-detail-open article.result-images[data-vim-selected] .detail { + top: 0; + .ltr-left(0); + + a.result-images-source { + display: flex; + flex-direction: column; + justify-content: center; + + img { + width: 100%; + max-height: calc(100vh - 24rem); + } + } + + a.result-detail-next { + .ltr-right(1rem); + } + } +} + +@media screen and (max-width: @phone) { + #results.image-detail-open article.result-images[data-vim-selected] .detail { + top: 0; + .ltr-left(0); + padding: 1rem; + + a.result-images-source img { + width: 100%; + max-height: calc(100vh - 20rem); + margin: 0; + } + + .result-images-labels p span { + width: inherit; + .ltr-margin-right(1rem); + } + } +} diff --git a/client/simple/src/less/embedded.less b/client/simple/src/less/embedded.less new file mode 100644 index 000000000..bd7b89ea6 --- /dev/null +++ b/client/simple/src/less/embedded.less @@ -0,0 +1,33 @@ +iframe[src^="https://w.soundcloud.com"] { + height: 120px; +} + +iframe[src^="https://www.deezer.com"] { + // The real size is 92px, but 94px are needed to avoid an inner scrollbar of + // the embedded HTML. + height: 94px; +} + +iframe[src^="https://www.mixcloud.com"] { + // the embedded player from mixcloud has some quirks: initial there is an + // issue with an image URL that is blocked since it is an a Cross-Origin + // request. The alternative text (Mixcloud Logo then cause an + // scrollbar in the inner of the iframe we can't avoid. Another quirk comes + // when pressing the play button, sometimes the shown player has an height of + // 200px, sometimes 250px. + height: 250px; +} + +iframe[src^="https://bandcamp.com/EmbeddedPlayer"] { + // show playlist + height: 350px; +} + +iframe[src^="https://bandcamp.com/EmbeddedPlayer/track"] { + // hide playlist + height: 120px; +} + +iframe[src^="https://genius.com/songs"] { + height: 65px; +} diff --git a/client/simple/src/less/index.less b/client/simple/src/less/index.less new file mode 100644 index 000000000..8590d0c3b --- /dev/null +++ b/client/simple/src/less/index.less @@ -0,0 +1,51 @@ +#main_index { + margin-top: 26vh; +} + +.index { + text-align: center; + + .title { + background: url('../img/searxng.png') no-repeat; + min-height: 4rem; + margin: 4rem auto; + background-position: center; + background-size: contain; + } + + h1 { + font-size: 4em; + visibility: hidden; + } + + #search, + #search_header { + margin: 0 auto; + background: inherit; + border: inherit; + padding: 0; + display: block; + } + + .search_filters { + display: block; + margin: 1em 0; + } + + .category label { + padding: 6px 10px; + border-bottom: initial !important; + } +} + +@media screen and (max-width: @tablet) { + div.title { + h1 { + font-size: 1em; + } + } + + #main_index { + margin-top: 6em; + } +} diff --git a/client/simple/src/less/info.less b/client/simple/src/less/info.less new file mode 100644 index 000000000..f354135d2 --- /dev/null +++ b/client/simple/src/less/info.less @@ -0,0 +1,10 @@ +.info-page { + code { + font-family: monospace; + .rounded-corners-tiny; + background-color: var(--color-doc-code-background); + color: var(--color-doc-code); + padding: 0.2rem; + border: 0 none; + } +} diff --git a/client/simple/src/less/mixins.less b/client/simple/src/less/mixins.less new file mode 100644 index 000000000..a4bae7128 --- /dev/null +++ b/client/simple/src/less/mixins.less @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +// Mixins +.text-size-adjust (@property: 100%) { + -webkit-text-size-adjust: @property; + -ms-text-size-adjust: @property; + -moz-text-size-adjust: @property; + text-size-adjust: @property; +} + +.rounded-corners (@radius: 10px) { + border-radius: @radius; +} + +.rounded-right-corners (@radius: 0 10px 10px 0) { + border-radius: @radius; +} + +.rounded-corners-tiny (@radius: 5px) { + border-radius: @radius; +} + +// disable user selection +.disable-user-select () { + -webkit-touch-callout: none; + user-select: none; +} + +.show-content-button() { + padding: 5px 10px; + .rounded-corners-tiny; + background: var(--color-show-btn-background); + color: var(--color-show-btn-font); + cursor: pointer; + + &:hover { + background: var(--color-btn-background); + color: var(--color-btn-font); + } +} diff --git a/client/simple/src/less/new_issue.less b/client/simple/src/less/new_issue.less new file mode 100644 index 000000000..e10a2791c --- /dev/null +++ b/client/simple/src/less/new_issue.less @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +.stats_endpoint { + .github-issue-button { + display: block; + font-size: 16px; + } + + .issue-hide { + display: none; + } + + input[type=checked] { + position: absolute; + } + + label { + margin: 1rem 1rem 1rem 0; + } + + .step_content { + margin: 1rem 1rem 1rem 2rem; + } + + .step1, + .step2 { + visibility: hidden; + } + + .step1_delay { + transition: visibility 0s linear 4s; + } + + #step1:checked ~ .step1, + #step2:checked ~ .step2 { + visibility: visible; + } +} diff --git a/client/simple/src/less/preferences.less b/client/simple/src/less/preferences.less new file mode 100644 index 000000000..497b2077a --- /dev/null +++ b/client/simple/src/less/preferences.less @@ -0,0 +1,233 @@ +table { + border-collapse: collapse; + + th, + td { + text-align: center; + padding: 1rem 0.5rem; + .ltr-text-align-left(); + } + + tr.pref-group th { + font-weight: normal; + .ltr-text-align-left(); + background: var(--color-settings-table-group-background); + } +} + +#main_preferences { + form { + width: 100%; + } + + fieldset { + margin: 8px; + border: none; + } + + legend { + margin: 0; + padding: 5px 0 0 0; + display: block; + .ltr-float-left(); + width: 300px; + } + + input[type="text"] { + width: 13.25rem; + color: var(--color-toolkit-input-text-font); + border: none; + background: none repeat scroll 0 0 var(--color-toolkit-select-background); + padding: 0.2rem 0.4rem; + height: 2rem; + .rounded-corners-tiny; + + &:hover, + &:focus { + background-color: var(--color-toolkit-select-background-hover); + } + } + + div.pref-group { + width: 100%; + font-weight: normal; + padding: 1rem 0.5rem; + .ltr-text-align-left(); + background: var(--color-settings-table-group-background); + } + + .value { + margin: 0; + padding: 0; + .ltr-float-left(); + width: 15em; + + select, + input[type="text"] { + font-size: inherit !important; + margin-top: 0; + .ltr-margin-right(1rem); + margin-bottom: 0; + .ltr-margin-left(0); + } + + select { + width: 14rem; + } + + select:focus, + input:focus { + outline: none; + box-shadow: 0 0 1px 1px var(--color-btn-background); + } + } + + .description { + margin: 0; + padding: 5px 0 0 0; + .ltr-float-right(); + width: 50%; + color: var(--color-settings-engine-description-font); + font-size: 90%; + } + + .bang { + .ltr-text-align-left(); + .rounded-corners-tiny; + background-color: var(--color-doc-code-background); + color: var(--color-doc-code); + padding: 0.2rem; + border: 0 none; + } + + .category { + .ltr-margin-right(0.5rem); + + label { + border: 2px solid transparent; + padding: 0.2rem 0.4rem; + .rounded-corners-tiny; + } + } + + .category input[type="checkbox"]:checked + label { + border: 2px solid var(--color-categories-item-border-selected); + } + + table.table_engines { + th.name { + /* stylelint-disable */ + label { + cursor: pointer; + } + /* stylelint-enable */ + + .engine-tooltip { + margin-top: 1.8rem; + .ltr-left(calc((100% - 85em) / 2 + 10em)); + max-width: 40rem; + + .engine-description { + margin-top: 0.5rem; + } + + .bang { + margin: 0.3rem; + } + } + } + + .checkbox-col, + .name, + .shortcut { + .ltr-text-align-left(); + } + } + + table.cookies { + width: 100%; + direction: ltr; + + th, + td { + text-align: left; + font-family: monospace; + font-size: 1rem; + padding: 0.5em; + vertical-align: top; + } + + td:first-child { + word-break: keep-all; + width: 14rem; + padding-right: 1rem; + } + + td:last-child { + word-break: break-all; + } + + & > tbody > tr:nth-child(even) > th, + & > tbody > tr:nth-child(even) > td { + background-color: var(--color-settings-tr-hover); + } + } + + .preferences_back { + background: none repeat scroll 0 0 var(--color-btn-background); + color: var(--color-btn-font); + border: 0 none; + .rounded-corners; + + cursor: pointer; + display: inline-block; + margin: 2px 4px; + padding: 0.7em; + + a { + color: var(--color-settings-return-font); + } + + a::first-letter { + text-transform: uppercase; + } + } + + #toggle-all-engines-container { + width: max-content; + margin-left: auto; + } + + div.selectable_url { + pre { + width: 100%; + } + } + + #copy-hash-container { + display: flex; + align-items: center; + gap: 0.5rem; + + div.selectable_url { + pre { + width: auto; + flex-grow: 1; + } + } + } + + #pref-hash-input { + width: 100%; + } +} + +@media screen and (max-width: @tablet) { + .preferences_back { + clear: both; + } + + .engine-tooltip { + .ltr-left(10em) !important; + } +} diff --git a/client/simple/src/less/result_templates.less b/client/simple/src/less/result_templates.less new file mode 100644 index 000000000..afabdf322 --- /dev/null +++ b/client/simple/src/less/result_templates.less @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +.osm-map-box { + height: 300px; + width: 100%; + margin: 10px 0; +} diff --git a/client/simple/src/less/rss.less b/client/simple/src/less/rss.less new file mode 100644 index 000000000..0bc6622e3 --- /dev/null +++ b/client/simple/src/less/rss.less @@ -0,0 +1,45 @@ +@import (inline) "../../node_modules/normalize.css/normalize.css"; +@import "definitions.less"; + +.text-size-adjust (@property: 100%) { + -webkit-text-size-adjust: @property; + -ms-text-size-adjust: @property; + -moz-text-size-adjust: @property; + text-size-adjust: @property; +} + +// Reset padding and margin +html, +body, +main { + padding: 0; + margin: 0; +} + +html { + font-family: sans-serif; + font-size: 0.9em; + .text-size-adjust; + + color: var(--color-base-font); + background-color: var(--color-base-background); + + scroll-behavior: smooth; +} + +body { + margin-inline: 1rem; +} + +a { + text-decoration: none; + color: var(--color-url-font); + + &:visited { + color: var(--color-url-visited-font); + + .highlight { + color: var(--color-url-visited-font); + } + } +} diff --git a/client/simple/src/less/search.less b/client/simple/src/less/search.less new file mode 100644 index 000000000..a46e4bf06 --- /dev/null +++ b/client/simple/src/less/search.less @@ -0,0 +1,393 @@ +/* +* SearXNG, A privacy-respecting, hackable metasearch engine +*/ + +#search { + padding: 0; + margin: 0; +} + +#search_header { + padding-top: 1.5em; + .ltr-padding-right(2em); + .ltr-padding-left(@results-offset - 3rem); + margin: 0; + background: var(--color-header-background); + border-bottom: 1px solid var(--color-header-border); + display: grid; + gap: 1rem 1.2rem; + grid-template-columns: 3rem 1fr; + grid-template-areas: + "logo search" + "spacer categories"; +} + +.category_checkbox, +.category_button { + display: inline-block; + position: relative; + .ltr-margin-right(1rem); + padding: 0; +} + +.category_checkbox { + input { + display: none; + } + + label { + svg { + padding-right: 0.2rem; + } + + cursor: pointer; + padding: 0.2rem 0; + display: inline-flex; + text-transform: capitalize; + font-size: 0.9em; + border-bottom: 2px solid transparent; + .disable-user-select; + + div.category_name { + margin: auto 0; + } + } + + input[type="checkbox"]:checked + label { + color: var(--color-categories-item-selected-font); + border-bottom: 2px solid var(--color-categories-item-border-selected); + } +} + +button.category_button { + background-color: inherit; + color: var(--color-base-font); + cursor: pointer; + padding: 0.2rem 0; + display: inline-flex; + align-items: center; + text-transform: capitalize; + font-size: 0.9em; + border: none; + border-bottom: 2px solid transparent; + + svg { + padding-right: 0.2rem; + } + + &.selected, + &:active { + color: var(--color-categories-item-selected-font); + border-bottom: 2px solid var(--color-categories-item-border-selected); + } +} + +// only used when JavaScript is disabled +.no-js #categories_container:has(button.category_button:focus-within) button.category_button { + &.selected { + color: var(--color-base-font); + border-bottom: none; + } + + &:focus-within { + color: var(--color-categories-item-selected-font); + border-bottom: 2px solid var(--color-categories-item-border-selected); + } +} + +#search_logo { + padding: 0.5rem 10px 0 10px; + grid-area: logo; + display: flex; + align-items: center; + justify-content: center; + + svg { + flex: 1; + width: 30px; + height: 30px; + margin: 0.5rem 0 auto 0; + } +} + +.search_categories { + grid-area: categories; + + .help { + display: none; + } + + &:hover .help { + display: block; + position: absolute; + background: var(--color-base-background); + padding: 1rem 0.6rem 0.6rem 0; + z-index: 1000; + width: 100%; + left: -0.1rem; + } +} + +#search_view { + padding: 0.5rem 0.3rem 0 0.5rem; + grid-area: search; + + body.results_endpoint & { + padding: 0.5rem 2.8rem 0 0; + } +} + +.search_box { + border-radius: 0.8rem; + width: @search-width; + display: inline-flex; + flex-direction: row; + white-space: nowrap; + box-shadow: var(--color-search-shadow); +} + +#clear_search { + display: block; + border-collapse: separate; + box-sizing: border-box; + width: 1.8rem; + margin: 0; + padding: 0.8rem 0.2rem; + background: none repeat scroll 0 0 var(--color-search-background); + border: none; + outline: none; + color: var(--color-search-font); + font-size: 1.1rem; + z-index: 1000; + + &:hover { + color: var(--color-search-background-hover); + } + + &.empty * { + display: none; + } +} + +html.no-js #clear_search.hide_if_nojs { + display: none; +} + +#q, +#send_search { + display: block; + margin: 0; + padding: 0.8rem; + background: none repeat scroll 0 0 var(--color-search-background); + border: none; + outline: none; + color: var(--color-search-font); + font-size: 1.1rem; + z-index: 100; +} + +#q { + width: 100%; + .ltr-padding-left(1rem); + .ltr-padding-right(0) !important; + .ltr-rounded-left-corners(0.8rem); +} + +#q::-ms-clear, +#q::-webkit-search-cancel-button { + display: none; +} + +#send_search { + .ltr-rounded-right-corners(0.8rem); + + &:hover { + cursor: pointer; + background-color: var(--color-search-background-hover); + color: var(--color-search-background); + } +} + +.no-js #clear_search, +.no-js #send_search { + width: auto !important; + .ltr-border-left(1px solid var(--color-search-border)); +} + +.search_filters { + margin-top: 0.6rem; + .ltr-margin-right(0); + margin-bottom: 0; + .ltr-margin-left(@results-offset + 0.6rem); + display: flex; + overflow-x: auto; + overscroll-behavior-inline: contain; + + select { + background-color: inherit; + + &:hover, + &:focus { + color: var(--color-base-font); + } + } +} + +@media screen and (max-width: @tablet) { + #search_header { + padding: 1.5em @results-tablet-offset 0 @results-tablet-offset; + column-gap: @results-tablet-offset; + } + + .search_filters { + margin-top: 0.6rem; + .ltr-margin-right(0); + margin-bottom: 0; + .ltr-margin-left(@results-tablet-offset + 3rem); + } + + #categories { + font-size: 90%; + clear: both; + } +} + +@media screen and (max-width: @tablet) and (hover: none) { + #main_index, + #main_results { + #categories_container { + width: max-content; + + .category_checkbox { + display: inline-block; + width: auto; + } + } + + #categories { + width: 100%; + .ltr-text-align-left(); + overflow: scroll hidden; + -webkit-overflow-scrolling: touch; + } + } +} + +@media screen and (max-width: @phone) { + #search_header { + width: 100%; + margin: 0; + padding: 0.1rem 0 0 0; + gap: 0 0; + grid-template-areas: + "logo search" + "categories categories"; + } + + .search_logo { + padding: 0; + } + + .search_box { + width: 98%; + display: flex; + } + + #q { + width: 100%; + flex: 1; + } + + .search_filters { + margin: 0 10px; + padding: 0.5rem 0; + } + + .category { + display: inline-block; + width: auto; + margin: 0; + + svg { + display: none; + } + } + + .category_checkbox { + label { + padding: 1rem !important; + margin: 0 !important; + } + } + + .category_button { + padding: 1rem !important; + margin: 0 !important; + } + + #search_view:focus-within { + display: block; + background-color: var(--color-search-background); + position: absolute; + top: 0; + height: 100%; + width: 100%; + z-index: 2000; + + .search_box { + border-bottom: 1px solid var(--color-search-border); + width: 100%; + border-radius: 0; + box-shadow: none; + + #send_search { + .ltr-margin-right(0) !important; // Delete when send_search button is disabled on mobile. + } + + * { + border: none; + border-radius: 0; + box-shadow: none; + } + } + } + + #main_results #q:placeholder-shown ~ #send_search { + .ltr-margin-right(2.6rem); + transition: margin 0.1s; + } +} + +@media screen and (max-width: @ultra-small-phone) { + #search_header { + grid-template-areas: + "search search" + "categories categories"; + } + + #search_logo { + display: none; + } +} + +#categories { + .disable-user-select; + + &::-webkit-scrollbar { + width: 0; + height: 0; + } +} + +#categories_container { + position: relative; +} + +.favicon img { + height: 1.5rem; + width: 1.5rem; + border-radius: 10%; + background-color: var(--color-favicon-background-color); + border: 1px solid var(--color-favicon-border-color); + display: flex; +} diff --git a/client/simple/src/less/stats.less b/client/simple/src/less/stats.less new file mode 100644 index 000000000..1e823f7c9 --- /dev/null +++ b/client/simple/src/less/stats.less @@ -0,0 +1,106 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +.engine-stats { + border-spacing: 0; + border-collapse: collapse; + + tr td, + tr th { + border-bottom: 1px solid var(--color-result-border); + padding: 0.25rem; + } + + table.engine-tooltip { + border-spacing: 0; + border-collapse: collapse; + + td, + th { + border: none; + } + } + + .engine-name { + width: 20rem; + } + + .engine-score { + width: 7rem; + text-align: right; + } + + .engine-reliability { + text-align: right; + } +} + +table.engine-error th.engine-error-type, +table.engine-error td.engine-error-type, +failed-test { + width: 10rem; +} + +.engine-errors { + margin-top: 3rem; + + table.engine-error { + max-width: 1280px; + margin: 1rem 0 3rem 0; + border: 1px solid var(--color-result-border); + .ltr-text-align-left(); + + tr th, + tr td { + padding: 0.5rem; + } + + & span.log_parameters { + border-right: 1px solid solid var(--color-result-border); + padding: 0 1rem 0 0; + margin: 0 0 0 0.5rem; + } + } +} + +.bar-chart-value { + width: 3em; + display: inline-block; + text-align: right; + padding-right: 0.5rem; +} + +.bar-chart-graph { + width: calc(100% - 5rem); + display: inline-block; +} + +.bar-chart-bar { + border: 3px solid var(--color-bar-chart-primary); + margin: 1px 0; +} + +.bar-chart-serie1 { + border: 3px solid var(--color-bar-chart-primary); + margin: 1px 0; + float: left; +} + +.bar-chart-serie2 { + border: 3px solid var(--color-bar-chart-secondary); + margin: 1px 0; + float: left; +} + +.bar0 { + width: 0; + border: 0; +} + +.generate-bar(100); + +.generate-bar(@n, @i: 1) when (@i =< @n) { + .bar@{i} { + width: (@i * 100% / @n); + } + .generate-bar(@n, (@i + 1)); +} diff --git a/client/simple/src/less/style-center.less b/client/simple/src/less/style-center.less new file mode 100644 index 000000000..3618bed86 --- /dev/null +++ b/client/simple/src/less/style-center.less @@ -0,0 +1,118 @@ +/* +--center-page-width overrides the less variable @results-width when the results are centered +see the CSS rules for #results in style.less ( grid-template-columns and gap). + +In this file, the --center-page-width values comes from the Oscar theme (Bootstrap 3). + +All rules starts with ".center-alignment-yes #main_results" to be enabled only +on the /search URL and when the "center alignment" preference is enabled. +*/ + +@media screen and (min-width: @phone) { + .center-alignment-yes #main_results { + --center-page-width: 48rem; + } +} + +@media screen and (width >= 62rem) { + .center-alignment-yes #main_results { + --center-page-width: 60rem; + } +} + +@media screen and (min-width: @tablet) { + .center-alignment-yes #main_results { + --center-page-width: 73rem; + } +} + +@media screen and (min-width: @phone) and (max-width: @tablet) { + // any change must be reset in @media screen and (min-width: @tablet) { ... } + .center-alignment-yes #main_results { + #results { + grid-template-columns: 60% calc(40% - @results-gap); + margin-left: 0; + margin-right: 0; + } + + #urls { + .ltr-margin-left(3rem); + } + + #sidebar { + .ltr-margin-right(1rem); + } + + #backToTop { + .ltr-left(calc(60% + 1rem)); + } + } +} + +@media screen and (min-width: @tablet) { + .center-alignment-yes #main_results { + display: flex; + flex-direction: column; + align-items: center; + + #search { + width: 100%; + display: flex; + flex-direction: column; + align-items: center; + } + + #search_header { + grid-template-columns: calc(50% - 4.5rem - var(--center-page-width) / 2) 3rem var(--center-page-width); + grid-template-areas: "na logo search" "na spacer categories"; + column-gap: 1.2rem; // from search.less + width: 100%; + padding-left: 0; + padding-right: 0; + } + + .search_filters { + .ltr-margin-left(0.5rem); + width: var(--center-page-width); + } + + #results { + // from style.less (when screen width = @tablet, reset layout from tablet) + .ltr-margin-right(2rem); + .ltr-margin-left(@results-offset); + // + + &.only_template_images, + &.image-detail-open { + // * grid-template-columns and .ltr-margin-left are set in style.less + // * With .image-detail-open.only_template_images, the width is set in detail.less + // * #results is going to be centered because of the #main_results rules, + // align-self aligns the results on the left or right according to the language. + align-self: flex-start; + } + + &:not(.only_template_images, .image-detail-open) { + // the gap is set in style.less + .ltr-margin-left(1.5rem); + grid-template-columns: calc(var(--center-page-width) - @results-gap - @results-sidebar-width) @results-sidebar-width; + + #backToTop { + .ltr-left(calc(50% - @results-sidebar-width - @results-gap + 1rem + var(--center-page-width) / 2)); + } + } + + .result .content { + max-width: inherit; + } + } + + // from style.less (when screen width = @tablet, reset layout from tablet) + #urls { + .ltr-margin-left(0); + } + + #sidebar { + .ltr-margin-right(0); + } + } +} diff --git a/client/simple/src/less/style-ltr.less b/client/simple/src/less/style-ltr.less new file mode 100644 index 000000000..6f7218b02 --- /dev/null +++ b/client/simple/src/less/style-ltr.less @@ -0,0 +1,85 @@ +.ltr-left(@offset) { + left: @offset; +} + +.ltr-right(@offset) { + right: @offset; +} + +.ltr-margin-right(@offset) { + margin-right: @offset; +} + +.ltr-margin-left(@offset) { + margin-left: @offset; +} + +.ltr-border-right(@offset) { + border-right: @offset; +} + +.ltr-border-left(@offset) { + border-left: @offset; +} + +.ltr-padding-right(@offset) { + padding-right: @offset; +} + +.ltr-padding-left(@offset) { + padding-left: @offset; +} + +.ltr-float-left() { + float: left; +} + +.ltr-float-right() { + float: right; +} + +.ltr-text-align-right() { + text-align: right; +} + +.ltr-rounded-left-corners(@radius) { + border-radius: @radius 0 0 @radius; +} + +.ltr-rounded-top-left-corners(@radius) { + border-radius: @radius 0 0 0; +} + +.ltr-rounded-bottom-left-corners(@radius) { + border-radius: 0 0 0 @radius; +} + +.ltr-rounded-right-corners(@radius) { + border-radius: 0 @radius @radius 0; +} + +.ltr-rounded-top-right-corners(@radius) { + border-radius: 0 @radius 0 0; +} + +.ltr-rounded-bottom-right-corners(@radius) { + border-radius: 0 0 @radius 0; +} + +.ltr-text-align-left() { + text-align: left; +} + +.ltr-border-left-width(@offset) { + border-left-width: @offset; +} + +.ltr-border-right-width(@offset) { + border-right-width: @offset; +} + +.ltr-transform() { + transform: scale(1, 1); +} + +@import "style.less"; diff --git a/client/simple/src/less/style-rtl.less b/client/simple/src/less/style-rtl.less new file mode 100644 index 000000000..8e2634d34 --- /dev/null +++ b/client/simple/src/less/style-rtl.less @@ -0,0 +1,159 @@ +.ltr-left(@offset) { + right: @offset; +} + +.ltr-right(@offset) { + left: @offset; +} + +.ltr-margin-right(@offset) { + margin-left: @offset; +} + +.ltr-margin-left(@offset) { + margin-right: @offset; +} + +.ltr-border-right(@offset) { + border-left: @offset; +} + +.ltr-border-left(@offset) { + border-right: @offset; +} + +.ltr-padding-right(@offset) { + padding-left: @offset; +} + +.ltr-padding-left(@offset) { + padding-right: @offset; +} + +.ltr-float-left() { + float: right; +} + +.ltr-float-right() { + float: left; +} + +.ltr-text-align-right() { + text-align: left; +} + +.ltr-rounded-left-corners(@radius) { + border-radius: 0 @radius @radius 0; +} + +.ltr-rounded-top-left-corners(@radius) { + border-radius: 0 @radius 0 0; +} + +.ltr-rounded-bottom-left-corners(@radius) { + border-radius: 0 0 @radius 0; +} + +.ltr-rounded-right-corners(@radius) { + border-radius: @radius 0 0 @radius; +} + +.ltr-rounded-top-right-corners(@radius) { + border-radius: @radius 0 0 0; +} + +.ltr-rounded-bottom-right-corners(@radius) { + border-radius: 0 0 0 @radius; +} + +.ltr-text-align-left() { + text-align: right; +} + +.ltr-border-left-width(@offset) { + border-right-width: @offset; +} + +.ltr-border-right-width(@offset) { + border-left-width: @offset; +} + +.ltr-transform() { + transform: scale(-1, 1); +} + +@import "style.less"; + +#q, +#sidebar .infobox dt bdi { + direction: rtl; +} + +// URL are displayed LTR but align on the right +#urls { + direction: initial; + text-align: right; + + .result .url_header { + direction: rtl; + } + + .result .url_wrapper { + justify-content: end; + } +} + +// Image flexbox +#main_results div#results.only_template_images #urls { + direction: rtl; +} + +// Image detail +#results.image-detail-open article.result-images[data-vim-selected] .detail .result-images-labels p { + direction: rtl; + + &.result-url { + // Display URL using the LTR direction + direction: ltr; + + span { + // And put the label on the right + direction: rtl; + float: right; + } + } +} + +// select HTML element +@supports ((background-position-x: 100%) and ((appearance: none) or (-webkit-appearance: none) or (-moz-appearance: none))) { + select { + border-width: 0 0 0 2rem; + background-position-x: -2rem; + } +} + +// vim hotkey helps is not translated +#vim-hotkeys-help table { + direction: ltr; + text-align: left; +} + +// Logo on the right +#main_preferences h1, +#main_stats h1 { + background-position-x: 100%; +} + +// patch of stats.less +.bar-chart-serie1, +.bar-chart-serie2 { + float: right; +} + +.engine-stats .engine-name, +.engine-stats .engine-score, +.engine-stats .result-count, +.engine-stats .response-time, +.engine-stats .engine-reliability { + text-align: right; +} diff --git a/client/simple/src/less/style.less b/client/simple/src/less/style.less new file mode 100644 index 000000000..7dca47402 --- /dev/null +++ b/client/simple/src/less/style.less @@ -0,0 +1,1162 @@ +/* +* SearXNG, A privacy-respecting, hackable metasearch engine +* +* To convert "style.less" to "style.css" run: $make styles +*/ + +// stylelint-disable no-descending-specificity + +@import (inline) "../../node_modules/normalize.css/normalize.css"; +@import "definitions.less"; +@import "mixins.less"; +@import "code.less"; +@import "toolkit.less"; +@import "autocomplete.less"; +@import "detail.less"; +@import "animations.less"; +@import "embedded.less"; +@import "info.less"; +@import "new_issue.less"; +@import "stats.less"; +@import "result_templates.less"; + +// for index.html template +@import "index.less"; + +// for preferences.html template +@import "preferences.less"; + +// Search-Field +@import "search.less"; + +// to center the results +@import "style-center.less"; + +// ion-icon +.ion-icon { + display: inline-block; + vertical-align: bottom; + line-height: 1; + text-decoration: inherit; + .ltr-transform(); +} + +.ion-icon-small { + width: 1rem; + height: 1rem; + .ion-icon; +} + +.ion-icon-big { + width: 1.5rem; + height: 1.5rem; + .ion-icon; +} + +// Main LESS-Code +html { + font-family: sans-serif; + font-size: 0.9em; + .text-size-adjust; + + color: var(--color-base-font); + background-color: var(--color-base-background); + padding: 0; + margin: 0; + + scroll-behavior: smooth; +} + +body, +main { + padding: 0; + margin: 0; +} + +body { + display: flex; + flex-direction: column; + height: 100vh; + margin: 0; +} + +main { + width: 100%; + margin-bottom: 2rem; + flex: 1; +} + +.page_with_header { + margin: 2em auto; + width: 85em; +} + +footer { + clear: both; + min-height: 4rem; + padding: 1rem 0; + width: 100%; + text-align: center; + background-color: var(--color-footer-background); + border-top: 1px solid var(--color-footer-border); + overflow: hidden; + + p { + font-size: 0.9em; + } +} + +.page_with_header .logo { + height: 40px; +} + +input[type="submit"], +#results button[type="submit"], +.button { + padding: 0.7rem; + display: inline-block; + background: var(--color-btn-background); + color: var(--color-btn-font); + .rounded-corners; + + border: 0; + cursor: pointer; +} + +a { + text-decoration: none; + color: var(--color-url-font); + + &:visited { + color: var(--color-url-visited-font); + + .highlight { + color: var(--color-url-visited-font); + } + } +} + +article[data-vim-selected] { + background: var(--color-result-vim-selected); + .ltr-border-left(0.2rem solid var(--color-result-vim-arrow)); + .ltr-rounded-right-corners(10px); +} + +article.result-images[data-vim-selected] { + background: var(--color-result-vim-arrow); + border: none; + .rounded-corners; + + .image_thumbnail { + filter: opacity(60%); + } + + span.title, + span.source { + color: var(--color-result-image-span-font-selected); + } +} + +article[data-vim-selected].category-videos, +article[data-vim-selected].category-news, +article[data-vim-selected].category-map, +article[data-vim-selected].category-music, +article[data-vim-selected].category-files, +article[data-vim-selected].category-social { + border: 1px solid var(--color-result-vim-arrow); + .rounded-corners; +} + +.result { + margin: @results-margin 0; + padding: @result-padding; + box-sizing: border-box; + width: 100%; + .ltr-border-left(0.2rem solid transparent); + + h3 { + font-size: 1.2rem; + word-wrap: break-word; + margin: 0.4rem 0 0.4rem 0; + padding: 0; + + a { + color: var(--color-result-link-font); + font-weight: normal; + font-size: 1.1em; + + &:visited { + color: var(--color-result-link-visited-font); + } + + &:focus, + &:hover { + text-decoration: underline; + border: none; + outline: none; + } + } + } + + .cache_link, + .proxyfied_link { + font-size: smaller !important; + margin-left: 0.5rem; + } + + .content, + .stat { + font-size: 0.9em; + margin: 0; + padding: 0; + max-width: 54em; + word-wrap: break-word; + line-height: 1.24; + + .highlight { + color: var(--color-result-description-highlight-font); + background: inherit; + font-weight: bold; + } + } + + .altlink a { + font-size: 0.9em; + margin: 0 10px 0 0; + .show-content-button; + } + + .codelines { + .highlight { + color: inherit; + background: inherit; + font-weight: normal; + } + } + + .url_header { + display: flex; + gap: 0.5rem; + } + + .url_wrapper { + display: flex; + align-items: center; + font-size: 1rem; + color: var(--color-result-url-font); + flex-flow: row nowrap; + overflow: hidden; + margin: 0; + padding: 0; + + .url_o1 { + white-space: nowrap; + flex-shrink: 1; + padding-bottom: 1px; + + .url_i1 { + unicode-bidi: plaintext; + } + } + + .url_o1::after { + content: " "; + width: 1ch; + display: inline-block; + } + + .url_o2 { + overflow: hidden; + white-space: nowrap; + flex: 0 1 content; + text-align: right; + padding-bottom: 1px; + + .url_i2 { + float: right; + } + } + } + + .published_date, + .result_length, + .result_views, + .result_author, + .result_shipping, + .result_source_country { + font-size: 0.8em; + color: var(--color-result-publishdate-font); + } + + .result_price { + font-size: 1.2em; + color: var(--color-result-description-highlight-font); + } + + img.thumbnail { + .ltr-float-left(); + padding-top: 0.6rem; + .ltr-padding-right(1rem); + width: 7rem; + height: unset; // remove height value that was needed for lazy loading + } + + .break { + clear: both; + } +} + +.result-paper, +.result-packages { + .attributes { + display: table; + border-spacing: 0.125rem; + + div { + display: table-row; + + span { + font-size: 0.9rem; + margin-top: 0.25rem; + display: table-cell; + + time { + font-size: 0.9rem; + } + } + + span:first-child { + color: var(--color-base-font); + min-width: 10rem; + } + + span:nth-child(2) { + color: var(--color-result-publishdate-font); + } + } + } + + .content { + margin-top: 0.25rem; + } + + .comments { + font-size: 0.9rem; + margin: 0.25rem 0 0 0; + padding: 0; + word-wrap: break-word; + line-height: 1.24; + font-style: italic; + } +} + +.result-packages { + .attributes { + margin-top: 0.3rem; + } +} + +.template_group_images { + display: flex; + flex-wrap: wrap; +} + +.template_group_images::after { + flex-grow: 10; + content: ""; +} + +.category-videos, +.category-news, +.category-map, +.category-music, +.category-files, +.category-social { + border: 1px solid var(--color-result-border); + margin: 0 @results-tablet-offset 1rem @results-tablet-offset !important; + .rounded-corners; +} + +.category-social .image { + width: auto !important; + min-width: 48px; + min-height: 48px; + padding: 0 5px 25px 0 !important; +} + +.audio-control audio { + width: 100%; + padding: 10px 0 0 0; +} + +.embedded-content iframe { + width: 100%; + padding: 10px 0 0 0; +} + +.result-videos { + img.thumbnail { + .ltr-float-left(); + padding-top: 0.6rem; + .ltr-padding-right(1rem); + width: 20rem; + height: unset; // remove height value that was needed for lazy loading + } +} + +.result-videos .content { + overflow: hidden; +} + +.result-videos .embedded-video iframe { + width: 100%; + aspect-ratio: 16 / 9; + padding: 10px 0 0 0; +} + +@supports not (aspect-ratio: 1 / 1) { + // support older browsers which do not have aspect-ratio + // https://caniuse.com/?search=aspect-ratio + .result-videos .embedded-video iframe { + height: calc(@results-width * 9 / 16); + } +} + +.engines { + .ltr-float-right(); + display: flex; + flex-wrap: wrap; + justify-content: flex-end; + color: var(--color-result-engines-font); + + span { + font-size: smaller; + margin-top: 0; + margin-bottom: 0; + .ltr-margin-right(0.5rem); + .ltr-margin-left(0); + } +} + +.small_font { + font-size: 0.8em; +} + +.highlight { + color: var(--color-result-link-font-highlight); + background: inherit; +} + +.empty_element { + font-style: italic; +} + +.result-images { + flex-grow: 1; + padding: 0.5rem 0.5rem 3rem 0.5rem; + margin: 0.25rem; + border: none !important; + height: @results-image-row-height; + width: unset; + + & > a { + position: relative; + outline: none; + } + + img { + margin: 0; + padding: 0; + border: none; + height: 100%; + width: 100%; + object-fit: cover; + vertical-align: bottom; + background: var(--color-result-image-background); + } + + .image_resolution { + position: absolute; + right: 0; + bottom: 0; + background: var(--color-image-resolution-background); + padding: 0.3rem 0.5rem; + font-size: 0.9rem; + color: var(--color-image-resolution-font); + border-top-left-radius: 0.3rem; + } + + span.title, + span.source { + display: block; + position: absolute; + + width: 100%; + font-size: 0.9rem; + color: var(--color-result-image-span-font); + padding: 0.5rem 0 0 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + span.source { + padding: 1.8rem 0 0 0; + font-size: 0.7rem; + } +} + +.result-map { + img.image { + .ltr-float-right() !important; + height: 100px !important; + width: auto !important; + } + + table { + font-size: 0.9em; + width: auto; + border-collapse: separate; + border-spacing: 0 0.35rem; + + th { + font-weight: inherit; + width: 17rem; + vertical-align: top; + .ltr-text-align-left(); + } + + td { + vertical-align: top; + .ltr-text-align-left(); + } + } +} + +.hidden { + display: none !important; +} + +#results { + margin-top: 1rem; + .ltr-margin-right(2rem); + margin-bottom: 0; + .ltr-margin-left(@results-offset); + display: grid; + grid-template: + "corrections sidebar" min-content + "answers sidebar" min-content + "urls sidebar" 1fr + "pagination sidebar" min-content + / @results-width @results-sidebar-width + ; + gap: 0 @results-gap; +} + +#results #sidebar *:first-child { + margin-top: 0; +} + +#urls { + padding: 0; + grid-area: urls; +} + +#apis .wrapper { + display: flex; +} + +#suggestions { + .wrapper { + display: flex; + flex-flow: column; + justify-content: flex-end; + + form { + display: inline-block; + flex: 1 1 50%; + } + } +} + +#suggestions, +#infoboxes { + input { + padding: 0; + margin: 3px; + font-size: 0.9em; + display: inline-block; + background: transparent; + color: var(--color-result-search-url-font); + cursor: pointer; + width: calc(100%); + text-overflow: ellipsis; + overflow: hidden; + text-align: left; + } + + input[type="submit"], + .infobox .url a { + color: var(--color-result-link-font); + text-decoration: none; + font-size: 0.9rem; + + &:hover { + text-decoration: underline; + } + } +} + +#corrections { + grid-area: corrections; + display: flex; + flex-flow: row wrap; + margin: 0 0 1em 0; + + h4, + input[type="submit"] { + display: inline-block; + padding: 0.5rem; + margin: 0.5rem; + } + + input[type="submit"] { + font-size: 0.8rem; + .rounded-corners-tiny; + } +} + +#infoboxes .title, +#suggestions .title, +#search_url .title, +#engines_msg .title, +#apis .title { + margin: 2em 0 0.5em 0; + color: var(--color-base-font); +} + +summary.title { + cursor: pointer; + padding-top: 1em; +} + +.sidebar-collapsible { + border-top: 1px solid var(--color-sidebar-border); + padding-bottom: 0.5em; +} + +#sidebar-end-collapsible { + border-bottom: 1px solid var(--color-sidebar-border); + width: 100%; +} + +#answers { + grid-area: answers; + background: var(--color-answer-background); + padding: @result-padding; + margin: 1rem 0; + margin-top: 0; + color: var(--color-answer-font); + .rounded-corners; + + h4 { + display: none; + } + + span { + overflow-wrap: anywhere; + } + + .answer { + display: flex; + flex-direction: column; + } + + .answer-url { + margin: 5px 10px 10px auto; + } +} + +#infoboxes { + form { + min-width: 210px; + } +} + +#sidebar { + grid-area: sidebar; + word-wrap: break-word; + color: var(--color-sidebar-font); + + .infobox { + margin: 10px 0 10px; + border: 1px solid var(--color-sidebar-border); + padding: 1rem; + font-size: 0.9em; + .rounded-corners; + + h2 { + margin: 0 0 0.5em 0; + } + + img { + max-width: 100%; + max-height: 12em; + display: block; + margin: 0 auto; + padding: 0; + } + + dt { + font-weight: bold; + } + + .attributes { + dl { + margin: 0.5em 0; + } + + dt { + display: inline; + margin-top: 0.5em; + .ltr-margin-right(0.25em); + margin-bottom: 0.5em; + .ltr-margin-left(0); + padding: 0; + } + + dd { + display: inline; + margin: 0.5em 0; + padding: 0; + } + } + + input { + font-size: 1em; + } + + br { + clear: both; + } + + .attributes, + .urls { + clear: both; + } + } +} + +#apis { + input { + font-size: 0.9em; + margin: 0 10px 0 0; + .show-content-button; + } +} + +#engines_msg { + .engine-name { + width: 10rem; + } + + .response-error { + color: var(--color-error); + } + + .bar-chart-value { + width: auto; + } +} + +#search_url { + div.selectable_url { + pre { + float: left; + width: 200em; + } + } + + button#copy_url { + float: right; + padding: 0.4rem; + margin-left: 0.5rem; + border-radius: 0.3rem; + display: none; // will be shown by JS. + } +} + +#links_on_top { + position: absolute; + .ltr-right(1rem); + .ltr-text-align-right(); + top: 2.7rem; + padding: 0; + border: 0; + display: flex; + align-items: center; + font-size: 1em; + color: var(--color-search-font); + + a { + display: flex; + align-items: center; + margin-left: 1em; + + svg { + font-size: 1.2em; + .ltr-margin-right(0.125em); + } + } + + a, + a:link *, + a:hover *, + a:visited *, + a:active * { + color: var(--color-search-font); + } +} + +#pagination { + grid-area: pagination; + + br { + clear: both; + } +} + +.numbered_pagination { + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + overflow: hidden; +} + +.page_number { + background: transparent !important; + color: var(--color-result-link-font) !important; + text-decoration: underline; +} + +.page_number_current { + background: transparent; + color: var(--color-result-link-visited-font); + border: none; +} + +#backToTop { + border: 1px solid var(--color-backtotop-border); + margin: 0; + padding: 0; + font-size: 1em; + background: var(--color-backtotop-background); + position: fixed; + bottom: 8rem; + .ltr-left(@results-width + @results-offset + (0.5 * @results-gap - 1.2em)); + transition: opacity 0.5s; + opacity: 0; + pointer-events: none; + .rounded-corners; + + a { + display: block; + margin: 0; + padding: 0.7em; + } + + a, + a:visited, + a:hover, + a:active { + color: var(--color-backtotop-font); + } +} + +#results.scrolling #backToTop { + opacity: 1; + pointer-events: all; +} + +/* + tablet layout +*/ + +.results-tablet() { + #links_on_top { + span { + display: none; + } + } + + .page_with_header { + margin: 2rem 0.5rem; + width: auto; + } + + #infoboxes { + position: inherit; + max-width: inherit; + + .infobox { + clear: both; + + img { + .ltr-float-left(); + max-width: 10em; + margin-top: 0.5em; + .ltr-margin-right(0.5em); + margin-bottom: 0.5em; + .ltr-margin-left(0); + } + } + } + + #sidebar { + margin: 0 @results-tablet-offset @results-margin @results-tablet-offset; + padding: 0; + float: none; + border: none; + width: auto; + + input { + border: 0; + } + } + + .result { + .thumbnail { + max-width: 98%; + } + + .url { + span.url { + display: block; + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; + width: 100%; + } + } + + .engines { + .ltr-float-right(); + display: flex; + flex-wrap: wrap; + justify-content: flex-end; + padding: 3px 0 0 0; + } + } + + .result-images { + border-bottom: none !important; + } + + .image_result { + max-width: 98%; + + img { + max-width: 98%; + } + } + + #backToTop { + display: none; + } + + #pagination { + margin: 2rem 0 0 0 !important; + } + + #main_results div#results { + margin: 0 auto; + justify-content: center; + display: grid; + grid-template: + "corrections" min-content + "answers" min-content + "sidebar" min-content + "urls" 1fr + "pagination" min-content + / @results-width + ; + gap: 0; + } +} + +@media screen and (width <= calc(@tablet - 0.5px)) { + #links_on_top { + span { + display: none; + } + } +} + +@media screen and (width <= 52rem) { + body.results_endpoint { + #links_on_top { + .link_on_top_about, + .link_on_top_donate { + display: none; + } + } + } +} + +@media screen and (min-width: @phone) and (max-width: @tablet) { + // when .center-alignment-yes, see style-center.less + // the media query includes "min-width: @phone" + // because the phone layout includes the tablet layout unconditionally. + .center-alignment-no { + .results-tablet(); + } +} + +/* Misc */ + +#main_results div#results.only_template_images { + margin: 1rem @results-tablet-offset 0 @results-tablet-offset; + display: grid; + grid-template: + "corrections" min-content + "answers" min-content + "sidebar" min-content + "urls" 1fr + "pagination" min-content + / 100%; + gap: 0; + + #sidebar { + display: none; + } + + #urls { + margin: 0; + display: flex; + flex-wrap: wrap; + } + + #urls::after { + flex-grow: 10; + content: ""; + } + + #backToTop { + .ltr-left(auto); + .ltr-right(1rem); + } + + #pagination { + .ltr-margin-right(4rem); + } +} + +/* + phone layout +*/ + +@media screen and (max-width: @phone) { + // based on the tablet layout + .results-tablet(); + + html { + background-color: var(--color-base-background-mobile); + } + + #main_results div#results { + grid-template-columns: 100%; + margin: 0 auto; + } + + #links_on_top { + top: 1.4rem; + .ltr-right(10px); + } + + #main_index #links_on_top { + top: 0.5rem; + .ltr-right(0.5rem); + } + + #results { + margin: 0; + padding: 0; + } + + #pagination { + margin: 2rem 1rem 0 1rem !important; + } + + article[data-vim-selected] { + border: 1px solid var(--color-result-vim-arrow); + .rounded-corners; + } + + .result { + background: var(--color-result-background); + border: 1px solid var(--color-result-background); + margin: 1rem 2%; + width: 96%; + .rounded-corners; + } + + .result-images { + margin: 0; + height: @results-image-row-height-phone; + background: var(--color-base-background-mobile); + width: unset; + } + + .infobox { + border: none !important; + background-color: var(--color-sidebar-background); + } + + .numbered_pagination { + display: none; + } + + .result-paper, + .result-packages { + .attributes { + display: block; + + div { + display: block; + + span { + display: inline; + } + + span:first-child { + font-weight: bold; + } + + span:nth-child(2) { + .ltr-margin-left(0.5rem); + } + } + } + } +} + +/* + small-phone layout +*/ + +@media screen and (max-width: @small-phone) { + .result-videos { + img.thumbnail { + float: none !important; + } + + .content { + overflow: inherit; + } + } +} + +pre code { + white-space: pre-wrap; +} diff --git a/client/simple/src/less/toolkit.less b/client/simple/src/less/toolkit.less new file mode 100644 index 000000000..d3fb2273b --- /dev/null +++ b/client/simple/src/less/toolkit.less @@ -0,0 +1,646 @@ +// other solution : http://stackoverflow.com/questions/1577598/how-to-hide-parts-of-html-when-javascript-is-disabled/13857783#13857783 + +// stylelint-disable no-descending-specificity + +html.no-js .hide_if_nojs { + display: none; +} + +html.js .show_if_nojs { + display: none; +} + +.center { + text-align: center; +} + +.right { + float: right; +} + +.left { + float: left; +} + +.invisible { + display: none !important; +} + +.list-unstyled { + list-style-type: none; + + li { + margin-top: 4px; + margin-bottom: 4px; + } +} + +.danger { + background-color: var(--color-error-background); +} + +.warning { + background: var(--color-warning-background); +} + +.success { + background: var(--color-success-background); +} + +.badge { + display: inline-block; + color: var(--color-toolkit-badge-font); + background-color: var(--color-toolkit-badge-background); + text-align: center; + white-space: nowrap; + vertical-align: baseline; + min-width: 10px; + padding: 1px 5px; + border-radius: 5px; +} + +// kbd +kbd { + padding: 2px 4px; + margin: 1px; + font-size: 90%; + color: var(--color-toolkit-kbd-font); + background: var(--color-toolkit-kbd-background); +} + +// table +table { + width: 100%; + + &.striped { + tr { + border-bottom: 1px solid var(--color-settings-tr-hover); + } + } +} + +th { + padding: 0.4em; +} + +td { + padding: 0 4px; +} + +tr { + &:hover { + background: var(--color-settings-tr-hover) !important; + } +} + +// pre +.pre() { + display: block; + font-size: 0.8em; + word-break: break-all; + margin: 0.1em; + user-select: all; +} + +div.selectable_url { + display: block; + border: 1px solid var(--color-result-search-url-border); + padding: 4px; + color: var(--color-result-search-url-font); + margin: 0.1em; + overflow: hidden; + height: 1.2em; + line-height: 1.2em; + .rounded-corners-tiny; + + pre { + .pre(); + } +} + +// dialog +.dialog() { + position: relative; + display: flex; + padding: 1rem; + margin: 0 0 1em 0; + border: 1px solid var(--color-toolkit-dialog-border); + .ltr-text-align-left(); + .rounded-corners; + + .close { + .ltr-float-right(); + position: relative; + top: -3px; + color: inherit; + font-size: 1.5em; + } + + ul, + ol, + p { + margin: 1px 0 0 0; + } + + table { + width: auto; + } + + tr { + vertical-align: text-top; + + &:hover { + background: transparent !important; + } + } + + td { + padding: 0 1em 0 0; + padding-top: 0; + .ltr-padding-right(1rem); + padding-bottom: 0; + .ltr-padding-left(0); + } + + h4 { + margin-top: 0.3em; + margin-bottom: 0.3em; + } +} + +.dialog-error { + .dialog(); + + color: var(--color-error); + background: var(--color-error-background); + border-color: var(--color-error); +} + +.dialog-error-block { + .dialog(); + + display: block; + color: var(--color-error); + background: var(--color-error-background); + border-color: var(--color-error); +} + +.dialog-warning { + .dialog(); + + color: var(--color-warning); + background: var(--color-warning-background); + border-color: var(--color-warning); +} + +.dialog-modal { + .dialog(); + + display: block; + background: var(--color-toolkit-dialog-background); + position: fixed; + top: 50%; + left: 50%; + margin: 0 auto; + transform: translate(-50%, -50%); + z-index: 5000; + + h3 { + margin-top: 0; + } +} + +// btn-collapse +.btn-collapse { + cursor: pointer; +} + +// +.scrollx { + overflow: auto hidden; + display: block; + padding: 0; + margin: 0; + border: none; +} + +/* -- tabs -- */ +.tabs .tabs > label { + font-size: 90%; +} + +ul.tabs { + border-bottom: 1px solid var(--color-toolkit-tabs-section-border); + list-style: none; + padding-left: 0; + + li { + display: flex; + } +} + +.tabs { + display: flex; + flex-wrap: wrap; + width: 100%; + min-width: 100%; + + & > * { + order: 2; + } + + & > input[type=radio] { + display: none; + } + + & > label, + & > li > a { + order: 1; + padding: 0.7em; + margin: 0 0.7em; + letter-spacing: 0.5px; + text-transform: uppercase; + border: solid var(--color-toolkit-tabs-label-border); + border-width: 0 0 2px 0; + color: unset; + + .disable-user-select(); + + cursor: pointer; + + &.active { + border-bottom: 2px solid var(--color-categories-item-border-selected); + background: var(--color-categories-item-selected); + color: var(--color-categories-item-selected-font); + } + } + + & > label:hover, + & > li > a:hover { + border-bottom: 2px solid var(--color-categories-item-border-selected); + } + + & > section { + min-width: 100%; + padding: 0.7rem 0; + box-sizing: border-box; + border-top: 1px solid var(--color-toolkit-tabs-section-border); + display: none; + } + + // default selection + & > label:last-of-type { + border-bottom: 2px solid var(--color-categories-item-border-selected); + background: var(--color-categories-item-selected); + color: var(--color-categories-item-selected-font); + letter-spacing: -0.1px; + } + + & > section:last-of-type { + display: block; + } +} + +html body .tabs > input:checked { + ~ section { + display: none; + } + + ~ label { + position: inherited; + background: inherit; + border-bottom: 2px solid transparent; + font-weight: normal; + color: inherit; + + &:hover { + border-bottom: 2px solid var(--color-categories-item-border-selected); + } + } + + + label { + border-bottom: 2px solid var(--color-categories-item-border-selected); + background: var(--color-categories-item-selected); + color: var(--color-categories-item-selected-font); + } + + + label + section { + display: block; + } +} + +/* -- select -- */ +select { + height: 2.4rem; + margin-top: 0; + .ltr-margin-right(1rem); + margin-bottom: 0; + .ltr-margin-left(0); + padding: 0.2rem !important; + color: var(--color-search-font); + font-size: 0.9rem; + z-index: 100; + + &:hover, + &:focus { + cursor: pointer; + } +} + +@supports ((background-position-x: 100%) and ((appearance: none) or (-webkit-appearance: none) or (-moz-appearance: none))) { + select { + appearance: none; + -webkit-appearance: none; + -moz-appearance: none; + border-width: 0 2rem 0 0; + border-color: transparent; + background: data-uri('image/svg+xml;charset=UTF-8', @select-light-svg-path) no-repeat; + background-position-x: calc(100% + 2rem); + background-size: 2rem; + background-origin: content-box; + background-color: var(--color-toolkit-select-background); + outline: medium none; + text-overflow: ellipsis; + .rounded-corners-tiny; + + &:hover, + &:focus { + background-color: var(--color-toolkit-select-background-hover); + } + + option { + background-color: var(--color-base-background); + } + } + + @media (prefers-color-scheme: dark) { + html.theme-auto select, + html.theme-dark select { + background-image: data-uri('image/svg+xml;charset=UTF-8', @select-dark-svg-path); + } + } + + html.theme-dark select { + background-image: data-uri('image/svg+xml;charset=UTF-8', @select-dark-svg-path); + } +} + +/* -- checkbox-onoff -- */ +input.checkbox-onoff[type="checkbox"] { + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + cursor: pointer; + display: inline-block; + width: 2.5em; + height: 0.7em; + box-shadow: none !important; + margin: 0 16px; + border-radius: 10px; + position: relative; + + /* focus & hover */ + &:focus, + &:hover { + outline: none; + } + + &:focus::after { + content: ""; + position: absolute; + width: 3.5em; + height: 1.65em; + border: 1px solid var(--color-btn-background); + border-radius: 12px; + box-shadow: var(--color-btn-background) 0 0 3px; + z-index: 1200; + top: -0.55em; + left: -0.6em; + } + + &::before { + position: absolute; + top: -0.5em; + display: flex; + justify-content: center; + align-items: center; + font-size: 0.75em; + width: 1.875em; + height: 1.875em; + border-radius: 50%; + } +} + +/* check mark +reversed-checkbox displays unchecked checkedboxes as checked, and vice versa. +see https://github.com/searxng/searxng/blob/3408d061aab9abc6168fec9bbc6deab71b236dac/searx/templates/simple/preferences.html#L313 +*/ +input.checkbox-onoff[type="checkbox"], +input.checkbox-onoff.reversed-checkbox[type="checkbox"]:checked { + background: var(--color-toolkit-checkbox-onoff-off-background); + + &::before { + left: -0.5em; + content: "\2715"; + color: var(--color-toolkit-checkbox-onoff-off-mark-color); + background: var(--color-toolkit-checkbox-onoff-off-mark-background); + } +} + +input.checkbox-onoff[type="checkbox"]:checked, +input.checkbox-onoff.reversed-checkbox[type="checkbox"] { + background: var(--color-toolkit-checkbox-onoff-on-background); + + &::before { + left: calc(100% - 1.5em); + content: "\2713"; + color: var(--color-toolkit-checkbox-onoff-on-mark-color); + background: var(--color-toolkit-checkbox-onoff-on-mark-background); + } +} + +/* -- checkbox -- */ +@supports (transform: rotate(-45deg)) { + input[type=checkbox]:not(.checkbox-onoff) { + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + + width: 20px; + height: 20px; + cursor: pointer; + position: relative; + top: 0; + left: 0; + border: 2px solid var(--color-toolkit-checkbox-input-border); + .rounded-corners(0.3em); + + &::after { + content: ''; + width: 9px; + height: 5px; + position: absolute; + top: 3px; + left: 2px; + border: 3px solid var(--color-toolkit-checkbox-label-border); + border-top: none; + border-right: none; + background: transparent; + opacity: 0; + transform: rotate(-45deg); + } + + &:checked::after { + border-color: var(--color-toolkit-checkbox-input-border); + opacity: 1; + } + } + + // disabled : can't be focused, show only the check mark + input[type=checkbox][disabled]:not(.checkbox-onoff) { + border: inherit; + background-color: transparent !important; + cursor: inherit; + } + + // if not checked and possible to checked then display a "light" check mark on hover + input.checkbox[type=checkbox]:not(:checked, [disabled], .checkbox-onoff):hover::after { + opacity: 0.5; + } +} + +@media screen and (max-width: @phone) { + .tabs > label { + width: 100%; + } +} + +/* -- loader -- */ +.loader, +.loader::after { + border-radius: 50%; + width: 2em; + height: 2em; +} + +.loader { + margin: 1em auto; + font-size: 10px; + position: relative; + text-indent: -9999em; + border-top: 0.5em solid var(--color-toolkit-loader-border); + border-right: 0.5em solid var(--color-toolkit-loader-border); + border-bottom: 0.5em solid var(--color-toolkit-loader-border); + border-left: 0.5em solid var(--color-toolkit-loader-borderleft); + -webkit-transform: translateZ(0); + -ms-transform: translateZ(0); + transform: translateZ(0); + -webkit-animation: load8 1.2s infinite linear; + animation: load8 1.2s infinite linear; +} + +@-webkit-keyframes load8 { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); + } + + 100% { + -webkit-transform: rotate(360deg); + transform: rotate(360deg); + } +} + +@keyframes load8 { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); + } + + 100% { + -webkit-transform: rotate(360deg); + transform: rotate(360deg); + } +} + +/* -- engine-tooltip -- */ +.engine-tooltip { + display: none; + position: absolute; + padding: 0.5rem 1rem; + margin: 0 0 0 2rem; + border: 1px solid var(--color-toolkit-engine-tooltip-border); + background: var(--color-toolkit-engine-tooltip-background); + font-size: 14px; + font-weight: normal; + z-index: 5000; + text-align: left; + .rounded-corners; +} + +th:hover .engine-tooltip, +td:hover .engine-tooltip, +.engine-tooltip:hover { + display: inline-block; +} + +/* -- stacked bar chart -- */ +.stacked-bar-chart { + margin: 0; + padding: 0 0.125rem 0 4rem; + width: 100%; + width: -moz-available; + width: -webkit-fill-available; + width: fill; + flex-flow: row nowrap; + align-items: center; + display: inline-flex; +} + +.stacked-bar-chart-value { + width: 3rem; + display: inline-block; + position: absolute; + padding: 0 0.5rem; + text-align: right; +} + +.stacked-bar-chart-base { + display: flex; + flex-shrink: 0; + flex-grow: 0; + flex-basis: unset; +} + +.stacked-bar-chart-median { + .stacked-bar-chart-base(); + + background: var(--color-base-font); + border: 1px solid rgba(var(--color-base-font-rgb), 0.9); + padding: 0.3rem 0; +} + +.stacked-bar-chart-rate80 { + .stacked-bar-chart-base(); + + background: transparent; + border: 1px solid rgba(var(--color-base-font-rgb), 0.3); + padding: 0.3rem 0; +} + +.stacked-bar-chart-rate95 { + .stacked-bar-chart-base(); + + background: transparent; + border-bottom: 1px dotted rgba(var(--color-base-font-rgb), 0.5); + padding: 0; +} + +.stacked-bar-chart-rate100 { + .stacked-bar-chart-base(); + + background: transparent; + border-left: 1px solid rgba(var(--color-base-font-rgb), 0.9); + padding: 0.4rem 0; + width: 1px; +} diff --git a/client/simple/src/less/toolkit_loader.less b/client/simple/src/less/toolkit_loader.less new file mode 100644 index 000000000..8b705e42b --- /dev/null +++ b/client/simple/src/less/toolkit_loader.less @@ -0,0 +1,29 @@ +.loader, +.loader::after { + border-radius: 50%; + width: 10em; + height: 10em; +} + +.loader { + margin: 60px auto; + font-size: 10px; + position: relative; + text-indent: -9999em; + border-top: 1.1em solid var(--color-loading-indicator); + border-right: 1.1em solid var(--color-loading-indicator); + border-bottom: 1.1em solid var(--color-loading-indicator); + border-left: 1.1em solid var(--color-loading-indicator-gap); + transform: translateZ(0); + animation: load8 1.1s infinite linear; +} + +@keyframes load8 { + 0% { + transform: rotate(0deg); + } + + 100% { + transform: rotate(360deg); + } +} diff --git a/client/simple/src/svg/information-circle-outline.svg b/client/simple/src/svg/information-circle-outline.svg new file mode 100644 index 000000000..b7c300094 --- /dev/null +++ b/client/simple/src/svg/information-circle-outline.svg @@ -0,0 +1,6 @@ + + Information Circle + + + + \ No newline at end of file diff --git a/client/simple/src/svg/select-dark.svg b/client/simple/src/svg/select-dark.svg new file mode 100644 index 000000000..97335cea5 --- /dev/null +++ b/client/simple/src/svg/select-dark.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/client/simple/src/svg/select-light.svg b/client/simple/src/svg/select-light.svg new file mode 100644 index 000000000..3b707f018 --- /dev/null +++ b/client/simple/src/svg/select-light.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file -- cgit v1.2.3