4 Commits

Author SHA1 Message Date
Markus Heiser
34728ae097 [build] /static 2025-07-03 17:35:05 +02:00
Ivan Gabaldon
879ac4e60f [mod] theme/simple: fmt/lint major pass
*Not so safe* changes, no behaviour changes.

- More ES5 to ES2015+ conversion.
- Make Biome not cry anymore applying remaining changes.
2025-07-03 17:35:05 +02:00
Ivan Gabaldon
95172213f6 [mod] theme/simple: fmt/lint minor pass
*Safe* changes, no behaviour changes.

- Initial ES5 to ES2015+ conversion.
- Plenty of styling diff changes.
2025-07-03 17:35:05 +02:00
Ivan Gabaldon
a947d5b3cf [mod] theme/simple: improve fmt/lint tech
This is one of various PR to refactor the simple theme internally.

Replace eslint tool with Biome. I have been using this for quite some time, and
it will help us to have more consistent and valid code without extending on
other third party plugins.

Removes unused dependencies.
2025-07-03 17:35:05 +02:00
29 changed files with 1513 additions and 4399 deletions

View File

@@ -83,7 +83,7 @@ MANAGE += py.build py.clean
MANAGE += pyenv pyenv.install pyenv.uninstall
MANAGE += format.python
MANAGE += test.yamllint test.pylint test.black test.pybabel test.unit test.coverage test.robot test.rst test.clean test.themes test.types.dev test.types.ci
MANAGE += themes.all themes.fix themes.test
MANAGE += themes.all themes.simple themes.fix themes.lint themes.test
MANAGE += static.build.commit static.build.drop static.build.restore
MANAGE += nvm.install nvm.clean nvm.status nvm.nodejs

View File

@@ -1,3 +1,2 @@
dist
node_modules
.stylelintcache

View File

@@ -1,17 +1,17 @@
{
"formatter": "unix",
"plugins": [ "stylelint-prettier" ],
"extends": [ "stylelint-config-standard-less" ],
"$schema": "https://json.schemastore.org/stylelintrc.json",
"plugins": ["stylelint-prettier"],
"extends": ["stylelint-config-standard-less"],
"rules": {
"prettier/prettier": true,
"at-rule-no-vendor-prefix": null,
"declaration-empty-line-before": null,
"no-invalid-position-at-import-rule": null,
"prettier/prettier": true,
"property-no-vendor-prefix": null,
"selector-no-vendor-prefix": null,
"selector-attribute-quotes": null,
"shorthand-property-no-redundant-values": null,
"at-rule-no-vendor-prefix": null,
"selector-class-pattern": null,
"selector-id-pattern": null,
"selector-class-pattern": null
"selector-no-vendor-prefix": null,
"shorthand-property-no-redundant-values": null
}
}

44
client/simple/biome.json Normal file
View File

@@ -0,0 +1,44 @@
{
"$schema": "https://biomejs.dev/schemas/2.0.6/schema.json",
"files": {
"includes": ["**", "!dist/**", "!node_modules/**"],
"ignoreUnknown": true
},
"vcs": {
"clientKind": "git",
"enabled": false,
"useIgnoreFile": true
},
"assist": {
"enabled": true,
"actions": {
"recommended": true
}
},
"formatter": {
"bracketSameLine": false,
"bracketSpacing": true,
"enabled": true,
"formatWithErrors": false,
"indentStyle": "space",
"indentWidth": 2,
"lineEnding": "lf",
"lineWidth": 120
},
"linter": {
"enabled": true,
"rules": {
"recommended": true
}
},
"javascript": {
"formatter": {
"arrowParentheses": "always",
"jsxQuoteStyle": "double",
"quoteProperties": "asNeeded",
"quoteStyle": "double",
"semicolons": "always",
"trailingCommas": "none"
}
}
}

View File

@@ -1,34 +0,0 @@
import globals from "globals";
import pluginJs from "@eslint/js";
/** @type {import('eslint').Linter.Config[]} */
export default [
pluginJs.configs.recommended,
// global "ignores"
// https://eslint.org/docs/latest/use/configure/configuration-files#globally-ignoring-files-with-ignores
{
ignores: ["node_modules/", "dist/"]
},
{
files: [
"**/*.js",
],
linterOptions: {
reportUnusedDisableDirectives: "error",
// noInlineConfig: true
},
languageOptions: {
sourceType: "module",
globals: {
...globals.browser,
}
},
rules: {
indent: ["error", 2],
},
},
];

File diff suppressed because it is too large Load Diff

View File

@@ -1,38 +1,36 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "simple",
"version": "1.0.0",
"private": true,
"license": "AGPL-3.0",
"type": "module",
"scripts": {
"build": "npm run build:icons && npm run build:vite",
"build:icons": "node theme_icons.js",
"build:vite": "vite build",
"clean": "rm -Rf node_modules",
"build": "node theme_icons.js && vite build",
"fix": "eslint --fix && stylelint --fix strict 'src/**/*.{css,scss,sass,less,styl,vue,svelte}'",
"icons.html": "node theme_icons.js"
"fix": "npm run fix:stylelint && npm run fix:biome && npm run fix:package",
"fix:biome": "biome check --write",
"fix:package": "sort-package-json --quiet",
"fix:stylelint": "stylelint --fix strict 'src/**/*.{scss,sass,less,styl}'",
"lint": "npm run lint:biome",
"lint:biome": "biome lint"
},
"devDependencies": {
"@eslint/js": "^9.29.0",
"copy-webpack-plugin": "^13.0.0",
"css-loader": "^7.1.2",
"@biomejs/biome": "~2.0.6",
"edge.js": "^6.2.1",
"eslint": "^9.29.0",
"filemanager-webpack-plugin": "^8.0.0",
"globals": "^16.2.0",
"ionicons": "^8.0.9",
"leaflet": "^1.9.4",
"less": "^4.3.0",
"less-loader": "^12.3.0",
"normalize.css": "^8.0.1",
"sharp": "^0.34.2",
"style-loader": "^4.0.0",
"sort-package-json": "^3.0.0",
"stylelint": "^16.21.0",
"stylelint-config-standard": "^38.0.0",
"stylelint-config-standard-less": "^3.0.1",
"stylelint-prettier": "^5.0.3",
"svgo": "^4.0.0",
"swiped-events": "^1.2.0",
"vite": "^6.3.5",
"vite-plugin-static-copy": "^3.1.0",
"vite-plugin-stylelint": "^6.0.0",
"webpack": "^5.99.9",
"webpack-cli": "^6.0.1"
"vite-plugin-static-copy": "^3.1.0"
}
}

View File

@@ -1,20 +1,19 @@
/* SPDX-License-Identifier: AGPL-3.0-or-later */
(function (w, d) {
'use strict';
((w, d) => {
// add data- properties
var script = d.currentScript || (function () {
var scripts = d.getElementsByTagName('script');
const getLastScriptElement = () => {
const scripts = d.getElementsByTagName("script");
return scripts[scripts.length - 1];
})();
};
const script = d.currentScript || getLastScriptElement();
w.searxng = {
settings: JSON.parse(atob(script.getAttribute('client_settings')))
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');
const htmlElement = d.getElementsByTagName("html")[0];
htmlElement.classList.remove("no-js");
htmlElement.classList.add("js");
})(window, document);

View File

@@ -4,29 +4,28 @@
* (C) Copyright Contributors to the searx project (2014 - 2021).
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
window.searxng = (function (w, d) {
'use strict';
window.searxng = ((w, d) => {
// 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];
};
((ElementPrototype) => {
ElementPrototype.matches =
ElementPrototype.matches ||
ElementPrototype.matchesSelector ||
ElementPrototype.webkitMatchesSelector ||
ElementPrototype.msMatchesSelector ||
function (selector) {
const nodes = (this.parentNode || this.document).querySelectorAll(selector);
let i = -1;
while (nodes[++i] && nodes[i] !== this);
return !!nodes[i];
};
})(Element.prototype);
}
function callbackSafe (callback, el, e) {
function callbackSafe(callback, el, e) {
try {
callback.call(el, e);
} catch (exception) {
@@ -34,41 +33,56 @@ window.searxng = (function (w, d) {
}
}
var searxng = window.searxng || {};
const searxng = window.searxng || {};
searxng.on = function (obj, eventType, callback, useCapture) {
searxng.on = (obj, eventType, callback, useCapture) => {
useCapture = useCapture || false;
if (typeof obj !== 'string') {
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);
d.addEventListener(
eventType,
(e) => {
let el = e.target || e.srcElement;
let found = false;
while (el?.matches && el !== d) {
found = el.matches(obj);
if (found) break;
el = el.parentElement;
}
if (found) {
callbackSafe(callback, el, e);
}
},
useCapture
);
}
};
searxng.ready = function (callback) {
if (document.readyState != 'loading') {
searxng.ready = (callback) => {
if (document.readyState !== "loading") {
callback.call(w);
} else {
w.addEventListener('DOMContentLoaded', callback.bind(w));
w.addEventListener("DOMContentLoaded", callback.bind(w));
}
};
searxng.http = function (method, url, data = null) {
return new Promise(function (resolve, reject) {
searxng.http = (method, url, data = null) =>
new Promise((resolve, reject) => {
try {
var req = new XMLHttpRequest();
const req = new XMLHttpRequest();
req.open(method, url, true);
req.timeout = 20000;
// On load
req.onload = function () {
if (req.status == 200) {
req.onload = () => {
if (req.status === 200) {
resolve(req.response, req.responseType);
} else {
reject(Error(req.statusText));
@@ -76,21 +90,21 @@ window.searxng = (function (w, d) {
};
// Handle network errors
req.onerror = function () {
req.onerror = () => {
reject(Error("Network Error"));
};
req.onabort = function () {
req.onabort = () => {
reject(Error("Transaction is aborted"));
};
req.ontimeout = function () {
req.ontimeout = () => {
reject(Error("Timeout"));
}
};
// Make the request
if (data) {
req.send(data)
req.send(data);
} else {
req.send();
}
@@ -98,65 +112,64 @@ window.searxng = (function (w, d) {
reject(ex);
}
});
};
searxng.loadStyle = function (src) {
var path = searxng.settings.theme_static_path + "/" + src,
id = "style_" + src.replace('.', '_'),
s = d.getElementById(id);
searxng.loadStyle = (src) => {
const path = `${searxng.settings.theme_static_path}/${src}`;
const id = `style_${src.replace(".", "_")}`;
let 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);
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);
searxng.loadScript = (src, callback) => {
const path = `${searxng.settings.theme_static_path}/${src}`;
const id = `script_${src.replace(".", "_")}`;
let s = d.getElementById(id);
if (s === null) {
s = d.createElement('script');
s.setAttribute('id', id);
s.setAttribute('src', path);
s = d.createElement("script");
s.setAttribute("id", id);
s.setAttribute("src", path);
s.onload = callback;
s.onerror = function () {
s.setAttribute('error', '1');
s.onerror = () => {
s.setAttribute("error", "1");
};
d.body.appendChild(s);
} else if (!s.hasAttribute('error')) {
} else if (!s.hasAttribute("error")) {
try {
callback.apply(s, []);
} catch (exception) {
console.log(exception);
}
} else {
console.log("callback not executed : script '" + path + "' not loaded.");
console.log(`callback not executed : script '${path}' not loaded.`);
}
};
searxng.insertBefore = function (newNode, referenceNode) {
searxng.insertBefore = (newNode, referenceNode) => {
referenceNode.parentNode.insertBefore(newNode, referenceNode);
};
searxng.insertAfter = function (newNode, referenceNode) {
searxng.insertAfter = (newNode, referenceNode) => {
referenceNode.parentNode.insertAfter(newNode, referenceNode.nextSibling);
};
searxng.on('.close', 'click', function () {
this.parentNode.classList.add('invisible');
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];
function getEndpoint() {
for (const className of d.getElementsByTagName("body")[0].classList.values()) {
if (className.endsWith("_endpoint")) {
return className.split("_")[0];
}
}
return '';
return "";
}
searxng.endpoint = getEndpoint();

View File

@@ -2,80 +2,77 @@
/* global searxng */
searxng.ready(function () {
'use strict';
searxng.ready(() => {
searxng.infinite_scroll_supported =
"IntersectionObserver" in window &&
"IntersectionObserverEntry" in window &&
"intersectionRatio" in window.IntersectionObserverEntry.prototype;
searxng.infinite_scroll_supported = (
'IntersectionObserver' in window &&
'IntersectionObserverEntry' in window &&
'intersectionRatio' in window.IntersectionObserverEntry.prototype);
if (searxng.endpoint !== 'results') {
if (searxng.endpoint !== "results") {
return;
}
if (!searxng.infinite_scroll_supported) {
console.log('IntersectionObserver not supported');
console.log("IntersectionObserver not supported");
return;
}
let d = document;
var onlyImages = d.getElementById('results').classList.contains('only_template_images');
const d = document;
const onlyImages = d.getElementById("results").classList.contains("only_template_images");
function newLoadSpinner () {
var loader = d.createElement('div');
loader.classList.add('loader');
function newLoadSpinner() {
const loader = d.createElement("div");
loader.classList.add("loader");
return loader;
}
function replaceChildrenWith (element, children) {
element.textContent = '';
children.forEach(child => element.appendChild(child));
function replaceChildrenWith(element, children) {
element.textContent = "";
children.forEach((child) => element.appendChild(child));
}
function loadNextPage (callback) {
var form = d.querySelector('#pagination form.next_page');
function loadNextPage(callback) {
const form = d.querySelector("#pagination form.next_page");
if (!form) {
return
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();
replaceChildrenWith(d.querySelector("#pagination"), [newLoadSpinner()]);
const formData = new FormData(form);
searxng
.http("POST", d.querySelector("#search").getAttribute("action"), formData)
.then((response) => {
const nextPageDoc = new DOMParser().parseFromString(response, "text/html");
const articleList = nextPageDoc.querySelectorAll("#urls article");
const paginationElement = nextPageDoc.querySelector("#pagination");
d.querySelector("#pagination").remove();
if (articleList.length > 0 && !onlyImages) {
// do not add <hr> element when there are only images
d.querySelector('#urls').appendChild(d.createElement('hr'));
d.querySelector("#urls").appendChild(d.createElement("hr"));
}
articleList.forEach(articleElement => {
d.querySelector('#urls').appendChild(articleElement);
articleList.forEach((articleElement) => {
d.querySelector("#urls").appendChild(articleElement);
});
if (paginationElement) {
d.querySelector('#results').appendChild(paginationElement);
d.querySelector("#results").appendChild(paginationElement);
callback();
}
}
).catch(
function (err) {
})
.catch((err) => {
console.log(err);
var e = d.createElement('div');
const 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 ]);
}
)
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",
rootMargin: "20rem"
};
const observedSelector = 'article.result:last-child';
const observer = new IntersectionObserver(entries => {
const observedSelector = "article.result:last-child";
const observer = new IntersectionObserver((entries) => {
const paginationEntry = entries[0];
if (paginationEntry.isIntersecting) {
observer.unobserve(paginationEntry.target);
@@ -84,5 +81,4 @@ searxng.ready(function () {
});
observer.observe(d.querySelector(observedSelector), intersectionObserveOptions);
}
});

View File

@@ -1,14 +1,13 @@
/* SPDX-License-Identifier: AGPL-3.0-or-later */
/* global searxng */
searxng.ready(function () {
function isElementInDetail (el) {
searxng.ready(() => {
function isElementInDetail(el) {
while (el !== undefined) {
if (el.classList.contains('detail')) {
if (el.classList.contains("detail")) {
return true;
}
if (el.classList.contains('result')) {
if (el.classList.contains("result")) {
// we found a result, no need to go to the root of the document:
// el is not inside a <div class="detail"> element
return false;
@@ -18,9 +17,9 @@ searxng.ready(function () {
return false;
}
function getResultElement (el) {
function getResultElement(el) {
while (el !== undefined) {
if (el.classList.contains('result')) {
if (el.classList.contains("result")) {
return el;
}
el = el.parentNode;
@@ -28,14 +27,14 @@ searxng.ready(function () {
return undefined;
}
function isImageResult (resultElement) {
return resultElement && resultElement.classList.contains('result-images');
function isImageResult(resultElement) {
return resultElement?.classList.contains("result-images");
}
searxng.on('.result', 'click', function (e) {
searxng.on(".result", "click", function (e) {
if (!isElementInDetail(e.target)) {
highlightResult(this)(true, true);
let resultElement = getResultElement(e.target);
const resultElement = getResultElement(e.target);
if (isImageResult(resultElement)) {
e.preventDefault();
searxng.selectImage(resultElement);
@@ -43,166 +42,179 @@ searxng.ready(function () {
}
});
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);
searxng.on(
".result a",
"focus",
(e) => {
if (!isElementInDetail(e.target)) {
const resultElement = getResultElement(e.target);
if (resultElement && resultElement.getAttribute("data-vim-selected") === null) {
highlightResult(resultElement)(true);
}
if (isImageResult(resultElement)) {
searxng.selectImage(resultElement);
}
}
if (isImageResult(resultElement)) {
searxng.selectImage(resultElement);
}
}
}, true);
},
true
);
/* common base for layouts */
var baseKeyBinding = {
'Escape': {
key: 'ESC',
const baseKeyBinding = {
Escape: {
key: "ESC",
fun: removeFocus,
des: 'remove focus from the focused input',
cat: 'Control'
des: "remove focus from the focused input",
cat: "Control"
},
'c': {
key: 'c',
c: {
key: "c",
fun: copyURLToClipboard,
des: 'copy url of the selected result to the clipboard',
cat: 'Results'
des: "copy url of the selected result to the clipboard",
cat: "Results"
},
'h': {
key: 'h',
h: {
key: "h",
fun: toggleHelp,
des: 'toggle help window',
cat: 'Other'
des: "toggle help window",
cat: "Other"
},
'i': {
key: 'i',
i: {
key: "i",
fun: searchInputFocus,
des: 'focus on the search input',
cat: 'Control'
des: "focus on the search input",
cat: "Control"
},
'n': {
key: 'n',
n: {
key: "n",
fun: GoToNextPage(),
des: 'go to next page',
cat: 'Results'
des: "go to next page",
cat: "Results"
},
'o': {
key: 'o',
o: {
key: "o",
fun: openResult(false),
des: 'open search result',
cat: 'Results'
des: "open search result",
cat: "Results"
},
'p': {
key: 'p',
p: {
key: "p",
fun: GoToPreviousPage(),
des: 'go to previous page',
cat: 'Results'
des: "go to previous page",
cat: "Results"
},
'r': {
key: 'r',
r: {
key: "r",
fun: reloadPage,
des: 'reload page from the server',
cat: 'Control'
des: "reload page from the server",
cat: "Control"
},
't': {
key: 't',
t: {
key: "t",
fun: openResult(true),
des: 'open the result in a new tab',
cat: 'Results'
},
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'
const 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),
ArrowRight: {
key: "→",
fun: highlightResult("down"),
des: "select next search result",
cat: "Results"
}
},
baseKeyBinding
),
'vim': Object.assign(
{ /* Vim-like Key Layout. */
'b': {
key: 'b',
vim: Object.assign(
{
/* Vim-like Key Layout. */
b: {
key: "b",
fun: scrollPage(-window.innerHeight),
des: 'scroll one page up',
cat: 'Navigation'
des: "scroll one page up",
cat: "Navigation"
},
'f': {
key: 'f',
f: {
key: "f",
fun: scrollPage(window.innerHeight),
des: 'scroll one page down',
cat: 'Navigation'
des: "scroll one page down",
cat: "Navigation"
},
'u': {
key: 'u',
u: {
key: "u",
fun: scrollPage(-window.innerHeight / 2),
des: 'scroll half a page up',
cat: 'Navigation'
des: "scroll half a page up",
cat: "Navigation"
},
'd': {
key: 'd',
d: {
key: "d",
fun: scrollPage(window.innerHeight / 2),
des: 'scroll half a page down',
cat: 'Navigation'
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'
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'
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'
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'
j: {
key: "j",
fun: highlightResult("down"),
des: "select next search result",
cat: "Results"
},
'y': {
key: 'y',
y: {
key: "y",
fun: copyURLToClipboard,
des: 'copy url of the selected result to the clipboard',
cat: 'Results'
},
}, baseKeyBinding)
}
des: "copy url of the selected result to the clipboard",
cat: "Results"
}
},
baseKeyBinding
)
};
var keyBindings = keyBindingLayouts[searxng.settings.hotkeys] || keyBindingLayouts.default;
const keyBindings = keyBindingLayouts[searxng.settings.hotkeys] || keyBindingLayouts.default;
searxng.on(document, "keydown", function (e) {
searxng.on(document, "keydown", (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
// biome-ignore lint/suspicious/noPrototypeBuiltins: FIXME: support for Chromium 93-87, Firefox 92-78, Safari 15.4-14
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') {
const tagName = e.target.tagName.toLowerCase();
if (e.key === "Escape") {
keyBindings[e.key].fun(e);
} else {
if (e.target === document.body || tagName === 'a' || tagName === 'button') {
if (e.target === document.body || tagName === "a" || tagName === "button") {
e.preventDefault();
keyBindings[e.key].fun();
}
@@ -210,13 +222,13 @@ searxng.ready(function () {
}
});
function highlightResult (which) {
return function (noScroll, keepFocus) {
var current = document.querySelector('.result[data-vim-selected]'),
function highlightResult(which) {
return (noScroll, keepFocus) => {
let current = document.querySelector(".result[data-vim-selected]"),
effectiveWhich = which;
if (current === null) {
// no selection : choose the first one
current = document.querySelector('.result');
current = document.querySelector(".result");
if (current === null) {
// no first one : there are no results
return;
@@ -227,48 +239,51 @@ searxng.ready(function () {
}
}
var next, results = document.querySelectorAll('.result');
results = Array.from(results); // convert NodeList to Array for further use
let next,
results = document.querySelectorAll(".result");
results = Array.from(results); // convert NodeList to Array for further use
if (typeof effectiveWhich !== 'string') {
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;
case "visible": {
const top = document.documentElement.scrollTop || document.body.scrollTop;
const 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;
for (let i = 0; i < results.length; i++) {
next = results[i];
const etop = next.offsetTop;
const ebot = etop + next.clientHeight;
if ((ebot <= bot) && (etop > top)) {
break;
if (ebot <= bot && etop > top) {
break;
}
}
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':
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;
// biome-ignore lint/complexity/noUselessSwitchCase: fallthrough is intended
case "top":
/* falls through */
default:
next = results[0];
default:
next = results[0];
}
}
if (next) {
current.removeAttribute('data-vim-selected');
next.setAttribute('data-vim-selected', 'true');
current.removeAttribute("data-vim-selected");
next.setAttribute("data-vim-selected", "true");
if (!keepFocus) {
var link = next.querySelector('h3 a') || next.querySelector('a');
const link = next.querySelector("h3 a") || next.querySelector("a");
if (link !== null) {
link.focus();
}
@@ -280,95 +295,95 @@ searxng.ready(function () {
};
}
function reloadPage () {
function reloadPage() {
document.location.reload(true);
}
function removeFocus (e) {
function removeFocus(e) {
const tagName = e.target.tagName.toLowerCase();
if (document.activeElement && (tagName === 'input' || tagName === 'select' || tagName === 'textarea')) {
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);
function pageButtonClick(css_selector) {
return () => {
const button = document.querySelector(css_selector);
if (button) {
button.click();
}
};
}
function GoToNextPage () {
function GoToNextPage() {
return pageButtonClick('nav#pagination .next_page button[type="submit"]');
}
function GoToPreviousPage () {
function GoToPreviousPage() {
return pageButtonClick('nav#pagination .previous_page button[type="submit"]');
}
function scrollPageToSelected () {
var sel = document.querySelector('.result[data-vim-selected]');
function scrollPageToSelected() {
const sel = document.querySelector(".result[data-vim-selected]");
if (sel === null) {
return;
}
var wtop = document.documentElement.scrollTop || document.body.scrollTop,
const 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)) {
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)) {
if (wtop > etop - offset) {
window.scroll(window.scrollX, etop - offset);
} else {
var wbot = wtop + wheight;
if (wbot < (ebot + offset)) {
const wbot = wtop + wheight;
if (wbot < ebot + offset) {
window.scroll(window.scrollX, ebot - wheight + offset);
}
}
}
function scrollPage (amount) {
return function () {
function scrollPage(amount) {
return () => {
window.scrollBy(0, amount);
highlightResult('visible')();
highlightResult("visible")();
};
}
function scrollPageTo (position, nav) {
return function () {
function scrollPageTo(position, nav) {
return () => {
window.scrollTo(0, position);
highlightResult(nav)();
};
}
function searchInputFocus () {
function searchInputFocus() {
window.scrollTo(0, 0);
var q = document.querySelector('#q');
const q = document.querySelector("#q");
q.focus();
if (q.setSelectionRange) {
var len = q.value.length;
const len = q.value.length;
q.setSelectionRange(len, len);
}
}
function openResult (newTab) {
return function () {
var link = document.querySelector('.result[data-vim-selected] h3 a');
function openResult(newTab) {
return () => {
let link = document.querySelector(".result[data-vim-selected] h3 a");
if (link === null) {
link = document.querySelector('.result[data-vim-selected] > a');
link = document.querySelector(".result[data-vim-selected] > a");
}
if (link !== null) {
var url = link.getAttribute('href');
const url = link.getAttribute("href");
if (newTab) {
window.open(url);
} else {
@@ -378,84 +393,81 @@ searxng.ready(function () {
};
}
function initHelpContent (divElement) {
var categories = {};
function initHelpContent(divElement) {
const categories = {};
for (var k in keyBindings) {
var key = keyBindings[k];
for (const k in keyBindings) {
const 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;
});
const sorted = Object.keys(categories).sort((a, b) => categories[b].length - categories[a].length);
if (sorted.length === 0) {
return;
}
var html = '<a href="#" class="close" aria-label="close" title="close">×</a>';
html += '<h3>How to navigate SearXNG with hotkeys</h3>';
html += '<table>';
let html = '<a href="#" class="close" aria-label="close" title="close">×</a>';
html += "<h3>How to navigate SearXNG with hotkeys</h3>";
html += "<table>";
for (var i = 0; i < sorted.length; i++) {
var cat = categories[sorted[i]];
for (let i = 0; i < sorted.length; i++) {
const cat = categories[sorted[i]];
var lastCategory = i === (sorted.length - 1);
var first = i % 2 === 0;
const lastCategory = i === sorted.length - 1;
const first = i % 2 === 0;
if (first) {
html += '<tr>';
html += "<tr>";
}
html += '<td>';
html += "<td>";
html += '<h4>' + cat[0].cat + '</h4>';
html += `<h4>${cat[0].cat}</h4>`;
html += '<ul class="list-unstyled">';
for (var cj in cat) {
html += '<li><kbd>' + cat[cj].key + '</kbd> ' + cat[cj].des + '</li>';
for (const cj in cat) {
html += `<li><kbd>${cat[cj].key}</kbd> ${cat[cj].des}</li>`;
}
html += '</ul>';
html += '</td>'; // col-sm-*
html += "</ul>";
html += "</td>"; // col-sm-*
if (!first || lastCategory) {
html += '</tr>'; // row
html += "</tr>"; // row
}
}
html += '</table>';
html += "</table>";
divElement.innerHTML = html;
}
function toggleHelp () {
var helpPanel = document.querySelector('#vim-hotkeys-help');
function toggleHelp() {
let 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';
helpPanel = document.createElement("div");
helpPanel.id = "vim-hotkeys-help";
helpPanel.className = "dialog-modal";
initHelpContent(helpPanel);
var body = document.getElementsByTagName('body')[0];
const body = document.getElementsByTagName("body")[0];
body.appendChild(helpPanel);
} else {
// toggle hidden
helpPanel.classList.toggle('invisible');
return;
helpPanel.classList.toggle("invisible");
}
}
function copyURLToClipboard () {
var currentUrlElement = document.querySelector('.result[data-vim-selected] h3 a');
function copyURLToClipboard() {
const currentUrlElement = document.querySelector(".result[data-vim-selected] h3 a");
if (currentUrlElement === null) return;
const url = currentUrlElement.getAttribute('href');
const url = currentUrlElement.getAttribute("href");
navigator.clipboard.writeText(url);
}
searxng.scrollPageToSelected = scrollPageToSelected;
searxng.selectNext = highlightResult('down');
searxng.selectPrevious = highlightResult('up');
searxng.selectNext = highlightResult("down");
searxng.selectPrevious = highlightResult("up");
});

View File

@@ -1,61 +1,64 @@
/* 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) {
((_w, _d, searxng) => {
searxng.ready(() => {
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);
const leaflet_target = this.dataset.leafletTarget;
const map_lon = parseFloat(this.dataset.mapLon);
const map_lat = parseFloat(this.dataset.mapLat);
const map_zoom = parseFloat(this.dataset.mapZoom);
const map_boundingbox = JSON.parse(this.dataset.mapBoundingbox);
const map_geojson = JSON.parse(this.dataset.mapGeojson);
searxng.loadStyle('css/leaflet.css');
searxng.loadScript('js/leaflet.js', function () {
var map_bounds = null;
searxng.loadStyle("css/leaflet.css");
searxng.loadScript("js/leaflet.js", () => {
let 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]);
const southWest = L.latLng(map_boundingbox[0], map_boundingbox[2]);
const northEast = L.latLng(map_boundingbox[1], map_boundingbox[3]);
map_bounds = L.latLngBounds(southWest, northEast);
}
// init map
var map = L.map(leaflet_target);
const 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 © <a href="https://openstreetmap.org">OpenStreetMap</a> 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 © <a href="https://openstreetmap.org">OpenStreetMap contributors</a>';
var osmWikimedia = new L.TileLayer(osmWikimediaUrl, {minZoom: 1, maxZoom: 19, attribution: osmWikimediaAttrib});
const osmMapnikUrl = "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png";
const osmMapnikAttrib = 'Map data © <a href="https://openstreetmap.org">OpenStreetMap</a> contributors';
const osmMapnik = new L.TileLayer(osmMapnikUrl, { minZoom: 1, maxZoom: 19, attribution: osmMapnikAttrib });
const osmWikimediaUrl = "https://maps.wikimedia.org/osm-intl/{z}/{x}/{y}.png";
const osmWikimediaAttrib =
'Wikimedia maps | Maps data © <a href="https://openstreetmap.org">OpenStreetMap contributors</a>';
const 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 () {
setTimeout(() => {
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);
map.setView(new L.LatLng(map_lat, map_lon), map_zoom);
} else {
map.setView(new L.latLng(map_lat, map_lon), 8);
map.setView(new L.LatLng(map_lat, map_lon), 8);
}
}
map.addLayer(osmMapnik);
var baseLayers = {
const baseLayers = {
"OSM Mapnik": osmMapnik,
"OSM Wikimedia": osmWikimedia,
"OSM Wikimedia": osmWikimedia
};
L.control.layers(baseLayers).addTo(map);

View File

@@ -1,21 +1,20 @@
/* SPDX-License-Identifier: AGPL-3.0-or-later */
(function (w, d, searxng) {
'use strict';
if (searxng.endpoint !== 'preferences') {
((_w, d, searxng) => {
if (searxng.endpoint !== "preferences") {
return;
}
searxng.ready(function () {
searxng.ready(() => {
let engine_descriptions = null;
function load_engine_descriptions () {
function load_engine_descriptions() {
if (engine_descriptions == null) {
searxng.http("GET", "engine_descriptions.json").then(function (content) {
searxng.http("GET", "engine_descriptions.json").then((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');
const elements = d.querySelectorAll(`[data-engine-name="${engine_name}"] .engine-description`);
for (const element of elements) {
let source = ' (<i>' + searxng.settings.translations.Source + ':&nbsp;' + description[1] + '</i>)';
const source = ` (<i>${searxng.settings.translations.Source}:&nbsp;${description[1]}</i>)`;
element.innerHTML = description[0] + source;
}
}
@@ -23,13 +22,13 @@
}
}
for (const el of d.querySelectorAll('[data-engine-name]')) {
searxng.on(el, 'mouseenter', load_engine_descriptions);
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 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
@@ -37,14 +36,14 @@
}
};
for (const el of enableAllEngines) {
searxng.on(el, 'click', () => toggleEngines(true));
searxng.on(el, "click", () => toggleEngines(true));
}
for (const el of disableAllEngines) {
searxng.on(el, 'click', () => toggleEngines(false));
searxng.on(el, "click", () => toggleEngines(false));
}
const copyHashButton = d.querySelector("#copy-hash");
searxng.on(copyHashButton, 'click', (e) => {
searxng.on(copyHashButton, "click", (e) => {
e.preventDefault();
navigator.clipboard.writeText(copyHashButton.dataset.hash);
copyHashButton.innerText = copyHashButton.dataset.copiedText;

View File

@@ -2,55 +2,54 @@
import "../../../node_modules/swiped-events/src/swiped-events.js";
(function (w, d, searxng) {
'use strict';
if (searxng.endpoint !== 'results') {
((w, d, searxng) => {
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}
));
searxng.ready(() => {
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";
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')) {
searxng.on(".btn-collapse", "click", function () {
const btnLabelCollapsed = this.getAttribute("data-btn-text-collapsed");
const btnLabelNotCollapsed = this.getAttribute("data-btn-text-not-collapsed");
const target = this.getAttribute("data-target");
const targetElement = d.querySelector(target);
let 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');
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');
searxng.on(".media-loader", "click", function () {
const target = this.getAttribute("data-target");
const iframe_load = d.querySelector(`${target} > iframe`);
const srctest = iframe_load.getAttribute("src");
if (srctest === null || srctest === undefined || srctest === false) {
iframe_load.setAttribute('src', iframe_load.getAttribute('data-src'));
iframe_load.setAttribute("src", iframe_load.getAttribute("data-src"));
}
});
searxng.on('#copy_url', 'click', function () {
var target = this.parentElement.querySelector('pre');
searxng.on("#copy_url", "click", function () {
const target = this.parentElement.querySelector("pre");
navigator.clipboard.writeText(target.innerText);
this.innerText = this.dataset.copiedText;
});
@@ -62,8 +61,8 @@ import "../../../node_modules/swiped-events/src/swiped-events.js";
let imgTimeoutID;
// progress spinner, while an image is loading
const imgLoaderSpinner = d.createElement('div');
imgLoaderSpinner.classList.add('loader');
const imgLoaderSpinner = d.createElement("div");
imgLoaderSpinner.classList.add("loader");
// singleton image object, which is used for all loading processes of a
// detailed image
@@ -89,15 +88,14 @@ import "../../../node_modules/swiped-events/src/swiped-events.js";
};
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');
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';
window.location.hash = "#image-viewer";
searxng.scrollPageToSelected();
@@ -105,21 +103,21 @@ import "../../../node_modules/swiped-events/src/swiped-events.js";
if (!resultElement) return;
// find <img> object in the element, if there is none, stop here.
const img = resultElement.querySelector('.result-images-source img');
const img = resultElement.querySelector(".result-images-source img");
if (!img) return;
// <img src="" data-src="http://example.org/image.jpg">
const src = img.getAttribute('data-src');
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');
const thumbnail = resultElement.querySelector(".image_thumbnail");
img.src = thumbnail.src;
// show a progress spinner
const detailElement = resultElement.querySelector('.detail');
const detailElement = resultElement.querySelector(".detail");
detailElement.appendChild(imgLoaderSpinner);
// load full size image in background
@@ -127,58 +125,58 @@ import "../../../node_modules/swiped-events/src/swiped-events.js";
// after the singelton loadImage has loaded the detail image into the
// cache, it can be used in the origin <img> as src property.
img.src = src;
img.removeAttribute('data-src');
img.removeAttribute("data-src");
});
};
searxng.closeDetail = function () {
d.getElementById('results').classList.remove('image-detail-open');
searxng.closeDetail = () => {
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();
if (window.location.hash === "#image-viewer") window.history.back();
searxng.scrollPageToSelected();
};
searxng.on('.result-detail-close', 'click', e => {
searxng.on(".result-detail-close", "click", (e) => {
e.preventDefault();
searxng.closeDetail();
});
searxng.on('.result-detail-previous', 'click', e => {
searxng.on(".result-detail-previous", "click", (e) => {
e.preventDefault();
searxng.selectPrevious(false);
});
searxng.on('.result-detail-next', 'click', e => {
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();
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);
});
}
);
d.querySelectorAll(".swipe-horizontal").forEach((obj) => {
obj.addEventListener("swiped-left", () => {
searxng.selectNext(false);
});
obj.addEventListener("swiped-right", () => {
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');
w.addEventListener(
"scroll",
() => {
const 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);
},
true
);
});
})(window, document, window.searxng);

View File

@@ -1,24 +1,23 @@
/* SPDX-License-Identifier: AGPL-3.0-or-later */
/* exported AutoComplete */
(function (w, d, searxng) {
'use strict';
var qinput_id = "q", qinput;
((_w, d, searxng) => {
const qinput_id = "q";
let qinput;
const isMobile = window.matchMedia("only screen and (max-width: 50em)").matches;
const isResultsPage = document.querySelector("main").id == "main_results";
const isResultsPage = document.querySelector("main").id === "main_results";
function submitIfQuery () {
if (qinput.value.length > 0) {
var search = document.getElementById('search');
function submitIfQuery() {
if (qinput.value.length > 0) {
const search = document.getElementById("search");
setTimeout(search.submit.bind(search), 0);
}
}
function createClearButton (qinput) {
var cs = document.getElementById('clear_search');
var updateClearButton = function () {
function createClearButton(qinput) {
const cs = document.getElementById("clear_search");
const updateClearButton = () => {
if (qinput.value.length === 0) {
cs.classList.add("empty");
} else {
@@ -28,31 +27,31 @@
// update status, event listener
updateClearButton();
cs.addEventListener('click', function (ev) {
qinput.value = '';
cs.addEventListener("click", (ev) => {
qinput.value = "";
qinput.focus();
updateClearButton();
ev.preventDefault();
});
qinput.addEventListener('input', updateClearButton, false);
qinput.addEventListener("input", updateClearButton, false);
}
const fetchResults = async (query) => {
let request;
if (searxng.settings.method === 'GET') {
if (searxng.settings.method === "GET") {
const reqParams = new URLSearchParams();
reqParams.append("q", query);
request = fetch("./autocompleter?" + reqParams.toString());
request = fetch(`./autocompleter?${reqParams.toString()}`);
} else {
const formData = new FormData();
formData.append("q", query);
request = fetch("./autocompleter", {
method: 'POST',
body: formData,
method: "POST",
body: formData
});
}
request.then(async function (response) {
request.then(async (response) => {
const results = await response.json();
if (!results) return;
@@ -63,30 +62,30 @@
autocompleteList.innerHTML = "";
// show an error message that no result was found
if (!results[1] || results[1].length == 0) {
if (!results[1] || results[1].length === 0) {
const noItemFoundMessage = document.createElement("li");
noItemFoundMessage.classList.add('no-item-found');
noItemFoundMessage.classList.add("no-item-found");
noItemFoundMessage.innerHTML = searxng.settings.translations.no_item_found;
autocompleteList.appendChild(noItemFoundMessage);
return;
}
for (let result of results[1]) {
for (const result of results[1]) {
const li = document.createElement("li");
li.innerText = result;
searxng.on(li, 'mousedown', () => {
searxng.on(li, "mousedown", () => {
qinput.value = result;
const form = d.querySelector("#search");
form.submit();
autocomplete.classList.remove('open');
autocomplete.classList.remove("open");
});
autocompleteList.appendChild(li);
}
});
};
searxng.ready(function () {
searxng.ready(() => {
// focus search input on large screens
if (!isMobile && !isResultsPage) document.getElementById("q").focus();
@@ -100,20 +99,20 @@
// autocompleter
if (searxng.settings.autocomplete) {
searxng.on(qinput, 'input', () => {
searxng.on(qinput, "input", () => {
const query = qinput.value;
if (query.length < searxng.settings.autocomplete_min) return;
setTimeout(() => {
if (query == qinput.value) fetchResults(query);
if (query === qinput.value) fetchResults(query);
}, 300);
});
searxng.on(qinput, 'keyup', (e) => {
searxng.on(qinput, "keyup", (e) => {
let currentIndex = -1;
const listItems = autocompleteList.children;
for (let i = 0; i < listItems.length; i++) {
if (listItems[i].classList.contains('active')) {
if (listItems[i].classList.contains("active")) {
currentIndex = i;
break;
}
@@ -121,22 +120,22 @@
let newCurrentIndex = -1;
if (e.key === "ArrowUp") {
if (currentIndex >= 0) listItems[currentIndex].classList.remove('active');
if (currentIndex >= 0) listItems[currentIndex].classList.remove("active");
// we need to add listItems.length to the index calculation here because the JavaScript modulos
// operator doesn't work with negative numbers
newCurrentIndex = (currentIndex - 1 + listItems.length) % listItems.length;
} else if (e.key === "ArrowDown") {
if (currentIndex >= 0) listItems[currentIndex].classList.remove('active');
if (currentIndex >= 0) listItems[currentIndex].classList.remove("active");
newCurrentIndex = (currentIndex + 1) % listItems.length;
} else if (e.key === "Tab" || e.key === "Enter") {
autocomplete.classList.remove('open');
autocomplete.classList.remove("open");
}
if (newCurrentIndex != -1) {
if (newCurrentIndex !== -1) {
const selectedItem = listItems[newCurrentIndex];
selectedItem.classList.add('active');
selectedItem.classList.add("active");
if (!selectedItem.classList.contains('no-item-found')) qinput.value = selectedItem.innerText;
if (!selectedItem.classList.contains("no-item-found")) qinput.value = selectedItem.innerText;
}
});
}
@@ -147,20 +146,20 @@
// filter (safesearch, time range or language) (this requires JavaScript
// though)
if (
qinput !== null
&& searxng.settings.search_on_category_select
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
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);
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) => {
for (const button of categoryButtons) {
searxng.on(button, "click", (event) => {
if (event.shiftKey) {
event.preventDefault();
button.classList.toggle("selected");
@@ -169,7 +168,7 @@
// manually deselect the old selection when a new category is selected
const selectedCategories = d.querySelectorAll("button.category_button.selected");
for (let categoryButton of selectedCategories) {
for (const categoryButton of selectedCategories) {
categoryButton.classList.remove("selected");
}
button.classList.add("selected");
@@ -179,12 +178,12 @@
// override form submit action to update the actually selected categories
const form = d.querySelector("#search");
if (form != null) {
searxng.on(form, 'submit', (event) => {
searxng.on(form, "submit", (event) => {
event.preventDefault();
const categoryValuesInput = d.querySelector("#selected-categories");
if (categoryValuesInput) {
let categoryValues = [];
for (let categoryButton of categoryButtons) {
const categoryValues = [];
for (const categoryButton of categoryButtons) {
if (categoryButton.classList.contains("selected")) {
categoryValues.push(categoryButton.name.replace("category_", ""));
}
@@ -195,5 +194,4 @@
});
}
});
})(window, document, window.searxng);

View File

@@ -2,71 +2,76 @@
* Generate icons.html for the jinja templates of the simple theme.
*/
import { argv } from "node:process";
import { dirname, resolve } from "node:path";
import { argv } from "node:process";
import { jinja_svg_sets } from "./tools/jinja_svg_catalog.js";
const HERE = dirname(argv[1]) + "/";
const HERE = `${dirname(argv[1])}/`;
const dest = resolve(HERE, "../../searx/templates/simple/icons.html");
/** @type import("./tools/jinja_svg_catalog.js").JinjaMacro[] */
const searxng_jinja_macros = [
{ name: "icon", class: "sxng-icon-set" },
{ name: "icon", class: "sxng-icon-set" },
{ name: "icon_small", class: "sxng-icon-set-small" },
{ name: "icon_big", class: "sxng-icon-set-big" },
{ name: "icon_big", class: "sxng-icon-set-big" }
];
const sxng_icon_opts ={
const sxng_icon_opts = {
multipass: true,
plugins: [
{ name: "removeTitle" },
{ name: "removeXMLNS" },
{ name: "addAttributesToSVGElement",
{
name: "addAttributesToSVGElement",
params: {
attributes: [
{
"aria-hidden": "true",
}]}}]
"aria-hidden": "true"
}
]
}
}
]
};
/** @type import("./tools/jinja_svg_catalog.js").IconSet */
/**
* @type import("./tools/jinja_svg_catalog.js").IconSet[]
*/
const simple_icons = [
{
base: resolve(HERE, "node_modules/ionicons/dist/svg"),
set: {
"alert": "alert-outline.svg",
"appstore": "apps-outline.svg",
"book": "book-outline.svg",
"close": "close-outline.svg",
"download": "download-outline.svg",
alert: "alert-outline.svg",
appstore: "apps-outline.svg",
book: "book-outline.svg",
close: "close-outline.svg",
download: "download-outline.svg",
"ellipsis-vertical": "ellipsis-vertical-outline.svg",
"file-tray-full": "file-tray-full-outline.svg",
"film": "film-outline.svg",
"globe": "globe-outline.svg",
"heart": "heart-outline.svg",
"image": "image-outline.svg",
"layers": "layers-outline.svg",
"leecher": "arrow-down.svg",
"location": "location-outline.svg",
"magnet": "magnet-outline.svg",
film: "film-outline.svg",
globe: "globe-outline.svg",
heart: "heart-outline.svg",
image: "image-outline.svg",
layers: "layers-outline.svg",
leecher: "arrow-down.svg",
location: "location-outline.svg",
magnet: "magnet-outline.svg",
"musical-notes": "musical-notes-outline.svg",
"navigate-down": "chevron-down-outline.svg",
"navigate-left": "chevron-back-outline.svg",
"navigate-right": "chevron-forward-outline.svg",
"navigate-up": "chevron-up-outline.svg",
"people": "people-outline.svg",
"play": "play-outline.svg",
"radio": "radio-outline.svg",
"save": "save-outline.svg",
"school": "school-outline.svg",
"search": "search-outline.svg",
"seeder": "swap-vertical.svg",
"settings": "settings-outline.svg",
"tv": "tv-outline.svg",
people: "people-outline.svg",
play: "play-outline.svg",
radio: "radio-outline.svg",
save: "save-outline.svg",
school: "school-outline.svg",
search: "search-outline.svg",
seeder: "swap-vertical.svg",
settings: "settings-outline.svg",
tv: "tv-outline.svg"
},
svgo_opts: sxng_icon_opts,
svgo_opts: sxng_icon_opts
},
// some of the ionicons are not suitable for a dark theme, we fixed the svg
// manually in src/svg/ionicons
@@ -75,9 +80,9 @@ const simple_icons = [
base: resolve(HERE, "src/svg/ionicons"),
set: {
"information-circle": "information-circle-outline.svg",
"newspaper": "newspaper-outline.svg",
newspaper: "newspaper-outline.svg"
},
svgo_opts: sxng_icon_opts,
svgo_opts: sxng_icon_opts
}
];

View File

@@ -1,5 +1,5 @@
import fs from "fs";
import path from "path";
import fs from "node:fs";
import path from "node:path";
import sharp from "sharp";
import { optimize as svgo } from "svgo";
@@ -9,70 +9,56 @@ import { optimize as svgo } from "svgo";
* @property {string} dest - Name of the destination file.
*/
/**
* Convert a list of SVG files to PNG.
*
* @param {Src2Dest[]} items - Array of SVG files (src: SVG, dest:PNG) to convert.
*/
async function svg2png(items) {
for (const item of items) {
try {
fs.mkdir(path.dirname(item.dest), { recursive: true }, (err) => {
if (err) throw err;
});
async function svg2png (items) {
items.forEach(
async (item) => {
try {
fs.mkdir(path.dirname(item.dest), { recursive: true }, (err) => {
if (err)
throw err;
});
const info = await sharp(item.src).png({
const info = await sharp(item.src)
.png({
force: true,
compressionLevel: 9,
palette: true,
}).toFile(item.dest);
palette: true
})
.toFile(item.dest);
console.log(
`[svg2png] created ${item.dest} -- bytes: ${info.size}, w:${info.width}px, h:${info.height}px`
);
} catch (err) {
console.error(`ERROR: ${item.dest} -- ${err}`);
throw(err);
}
console.log(`[svg2png] created ${item.dest} -- bytes: ${info.size}, w:${info.width}px, h:${info.height}px`);
} catch (err) {
console.error(`ERROR: ${item.dest} -- ${err}`);
throw err;
}
);
}
}
/**
* Optimize SVG images for WEB.
*
* @param {import('svgo').Config} svgo_opts - Options passed to svgo.
* @param {Src2Dest[]} items - Array of SVG files (src:SVG, dest:SVG) to optimize.
* @param {import('svgo').Config} svgo_opts - Options passed to svgo.
*/
async function svg2svg(items, svgo_opts) {
for (const item of items) {
try {
fs.mkdir(path.dirname(item.dest), { recursive: true }, (err) => {
if (err) throw err;
});
async function svg2svg(svgo_opts, items) {
items.forEach(
async (item) => {
try {
fs.mkdir(path.dirname(item.dest), { recursive: true }, (err) => {
if (err)
throw err;
});
const raw = fs.readFileSync(item.src, "utf8");
const opt = svgo(raw, svgo_opts);
fs.writeFileSync(item.dest, opt.data);
console.log(
`[svg2svg] optimized: ${item.dest} -- src: ${item.src}`
);
} catch (err) {
console.error(`ERROR: optimize src: ${item.src} -- ${err}`);
throw(err);
}
const raw = fs.readFileSync(item.src, "utf8");
const opt = svgo(raw, svgo_opts);
fs.writeFileSync(item.dest, opt.data);
console.log(`[svg2svg] optimized: ${item.dest} -- src: ${item.src}`);
} catch (err) {
console.error(`ERROR: optimize src: ${item.src} -- ${err}`);
throw err;
}
);
}
}
export { svg2png, svg2svg };

View File

@@ -1,8 +1,8 @@
import fs from "fs";
import { resolve, dirname } from "path";
import { Edge } from 'edge.js';
import fs from "node:fs";
import { dirname, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import { Edge } from "edge.js";
import { optimize as svgo } from "svgo";
import { fileURLToPath } from 'url';
const __dirname = dirname(fileURLToPath(import.meta.url));
const __jinja_class_placeholder__ = "__jinja_class_placeholder__";
@@ -11,7 +11,7 @@ const __jinja_class_placeholder__ = "__jinja_class_placeholder__";
/**
* @typedef {object} IconSet - A set of icons
* @property {object[]} set - Array of SVG icons, where property name is the
* @property {object} set - Object of SVG icons, where property name is the
* name of the icon and value is the src of the SVG (relative to base).
* @property {string} base - Folder in which the SVG src files are located.
* @property {import("svgo").Config} svgo_opts - svgo options for this set.
@@ -30,7 +30,6 @@ const __jinja_class_placeholder__ = "__jinja_class_placeholder__";
* @property {string} class - SVG's class name (value of XML class attribute)
*/
// -- functions
/**
@@ -43,34 +42,30 @@ const __jinja_class_placeholder__ = "__jinja_class_placeholder__";
*/
function jinja_svg_catalog(dest, macros, items) {
const svg_catalog = {};
const edge_template = resolve(__dirname, "jinja_svg_catalog.html.edge");
items.forEach(
(item) => {
/** @type {import("svgo").Config} */
// JSON.stringify & JSON.parse are used to create a deep copy of the
// item.svgo_opts object
const svgo_opts = JSON.parse(JSON.stringify(item.svgo_opts));
svgo_opts.plugins.push({
name: "addClassesToSVGElement",
params: {
classNames: [__jinja_class_placeholder__]
}}
);
try {
const raw = fs.readFileSync(item.src, "utf8");
const opt = svgo(raw, svgo_opts);
svg_catalog[item.name] = opt.data;
} catch (err) {
console.error(`ERROR: jinja_svg_catalog processing ${item.name} src: ${item.src} -- ${err}`);
throw(err);
items.forEach((item) => {
/** @type {import("svgo").Config} */
// JSON.stringify & JSON.parse are used to create a deep copy of the
// item.svgo_opts object
const svgo_opts = JSON.parse(JSON.stringify(item.svgo_opts));
svgo_opts.plugins.push({
name: "addClassesToSVGElement",
params: {
classNames: [__jinja_class_placeholder__]
}
});
try {
const raw = fs.readFileSync(item.src, "utf8");
const opt = svgo(raw, svgo_opts);
svg_catalog[item.name] = opt.data;
} catch (err) {
console.error(`ERROR: jinja_svg_catalog processing ${item.name} src: ${item.src} -- ${err}`);
throw err;
}
);
});
fs.mkdir(dirname(dest), { recursive: true }, (err) => {
if (err) throw err;
@@ -82,20 +77,16 @@ function jinja_svg_catalog(dest, macros, items) {
edge_template: edge_template,
__jinja_class_placeholder__: __jinja_class_placeholder__,
// see https://github.com/edge-js/edge/issues/162
open_curly_brace : "{{",
close_curly_brace : "}}"
open_curly_brace: "{{",
close_curly_brace: "}}"
};
const jinjatmpl = Edge.create().renderRawSync(
fs.readFileSync(edge_template, "utf-8"),
ctx
);
const jinjatmpl = Edge.create().renderRawSync(fs.readFileSync(edge_template, "utf-8"), ctx);
fs.writeFileSync(dest, jinjatmpl);
console.log(`[jinja_svg_catalog] created: ${dest}`);
}
/**
* Calls jinja_svg_catalog for a collection of icon sets where each set has its
* own parameters.
@@ -109,7 +100,6 @@ function jinja_svg_sets(dest, macros, sets) {
const items = [];
const all = [];
for (const obj of sets) {
for (const [name, file] of Object.entries(obj.set)) {
if (all.includes(name)) {
throw new Error(`ERROR: ${name} has already been defined`);
@@ -117,7 +107,7 @@ function jinja_svg_sets(dest, macros, sets) {
items.push({
name: name,
src: resolve(obj.base, file),
svgo_opts: obj.svgo_opts,
svgo_opts: obj.svgo_opts
});
}
jinja_svg_catalog(dest, macros, items);
@@ -126,7 +116,4 @@ function jinja_svg_sets(dest, macros, sets) {
// -- exports
export {
jinja_svg_sets,
jinja_svg_catalog,
};
export { jinja_svg_sets, jinja_svg_catalog };

View File

@@ -8,33 +8,36 @@
* needed.
*/
import { svg2png } from "./img.js";
import { svg2svg } from "./img.js";
import { svg2png, svg2svg } from "./img.js";
/**
* Vite plugin to convert a list of SVG files to PNG.
*
* @param {import('./img.js').Src2Dest} items - Array of SVG files (src: SVG, dest:PNG) to convert.
* @param {import('./img.js').Src2Dest[]} items - Array of SVG files (src: SVG, dest:PNG) to convert.
*/
function plg_svg2png(items) {
return {
name: 'searxng-simple-svg2png',
apply: 'build', // or 'serve'
async writeBundle() { svg2png(items); },
name: "searxng-simple-svg2png",
apply: "build", // or 'serve'
async writeBundle() {
await svg2png(items);
}
};
}
/**
* Vite plugin to optimize SVG images for WEB.
*
* @param {import('./img.js').Src2Dest[]} items - Array of SVG files (src:SVG, dest:SVG) to optimize.
* @param {import('svgo').Config} svgo_opts - Options passed to svgo.
* @param {import('./img.js').Src2Dest} items - Array of SVG files (src:SVG, dest:SVG) to optimize.
*/
function plg_svg2svg(svgo_opts, items) {
function plg_svg2svg(items, svgo_opts) {
return {
name: 'searxng-simple-svg2png',
apply: 'build', // or 'serve'
async writeBundle() { svg2svg(items, svgo_opts); },
name: "searxng-simple-svg2png",
apply: "build", // or 'serve'
async writeBundle() {
await svg2svg(items, svgo_opts);
}
};
}

View File

@@ -4,16 +4,12 @@
import { resolve } from "node:path";
import { defineConfig } from "vite";
import stylelint from "vite-plugin-stylelint";
import { viteStaticCopy } from "vite-plugin-static-copy";
import { plg_svg2png } from "./tools/plg.js";
import { plg_svg2svg } from "./tools/plg.js";
import { plg_svg2png, plg_svg2svg } from "./tools/plg.js";
const ROOT = "../.."; // root of the git reposetory
const ROOT = "../.."; // root of the git reposetory
const PATH = {
dist: resolve(ROOT, "searx/static/themes/simple"),
// dist: resolve(ROOT, "client/simple/dist"),
@@ -22,27 +18,24 @@ const PATH = {
brand: "src/brand",
static: resolve(ROOT, "client/simple/static"),
leaflet: resolve(ROOT, "client/simple/node_modules/leaflet/dist"),
templates: resolve(ROOT, "searx/templates/simple"),
templates: resolve(ROOT, "searx/templates/simple")
};
/**
* @type {import('svgo').Config}
*/
const svg2svg_opts = {
plugins: [
{ name: "preset-default" },
"sortAttrs",
"convertStyleToAttrs",
]
plugins: [{ name: "preset-default" }, "sortAttrs", "convertStyleToAttrs"]
};
/**
* @type {import('svgo').Config}
*/
const svg2svg_favicon_opts = {
plugins: [
{ name: "preset-default" },
"sortAttrs",
]
plugins: [{ name: "preset-default" }, "sortAttrs"]
};
export default defineConfig({
root: PATH.src,
mode: "production",
// mode: "development",
@@ -63,21 +56,25 @@ export default defineConfig({
// FIXME: missing CCS sourcemaps!!
sourceMap: {
outputSourceFiles: true,
sourceMapURL: (name) => { const s = name.split('/'); return s[s.length - 1] + '.map'; },
},
sourceMapURL: (name) => {
const s = name.split("/");
return `${s[s.length - 1]}.map`;
}
}
// env: 'development',
// relativeUrls: true,
// javascriptEnabled: true,
},
},
}, // end: css
}
}
}, // end: css
esbuild : {
esbuild: {
// FIXME: missing CCS sourcemaps!!
sourcemap: true
},
build: {
target: "modules",
manifest: "manifest.json",
emptyOutDir: true,
assetsDir: "",
@@ -93,47 +90,37 @@ export default defineConfig({
rollupOptions: {
input: {
// build CSS files
"css/searxng.min.css": PATH.src + "/less/style-ltr.less",
"css/searxng-rtl.min.css": PATH.src + "/less/style-rtl.less",
"css/rss.min.css": PATH.src + "/less/rss.less",
"css/searxng.min.css": `${PATH.src}/less/style-ltr.less`,
"css/searxng-rtl.min.css": `${PATH.src}/less/style-rtl.less`,
"css/rss.min.css": `${PATH.src}/less/rss.less`,
// build JS files
"js/searxng.head.min": PATH.src + "/js/searxng.head.js",
"js/searxng.min": PATH.src + "/js/searxng.js",
"js/searxng.head.min": `${PATH.src}/js/searxng.head.js`,
"js/searxng.min": `${PATH.src}/js/searxng.js`
},
// file naming conventions / pathnames are relative to outDir (PATH.dist)
output: {
entryFileNames: "[name].js",
chunkFileNames: "[name].js",
assetFileNames: "[name].[ext]",
assetFileNames: "[name].[ext]"
// Vite does not support "rollupOptions.output.sourcemap".
// Please use "build.sourcemap" instead.
// sourcemap: true,
},
},
}, // end: build
}
}
}, // end: build
plugins: [
stylelint({
build: true,
emitWarningAsError: true,
fix: true,
}),
// Leaflet
viteStaticCopy({
targets: [
{ src: PATH.leaflet + "/leaflet.{js,js.map}", dest: PATH.dist + "/js" },
{ src: PATH.leaflet + "/images/*.png", dest: PATH.dist + "/css/images/" },
{ src: PATH.leaflet + "/*.{css,css.map}", dest: PATH.dist + "/css" },
{ src: PATH.static + "/**/*", dest: PATH.dist },
{ src: `${PATH.leaflet}/leaflet.{js,js.map}`, dest: `${PATH.dist}/js` },
{ src: `${PATH.leaflet}/images/*.png`, dest: `${PATH.dist}/css/images/` },
{ src: `${PATH.leaflet}/*.{css,css.map}`, dest: `${PATH.dist}/css` },
{ src: `${PATH.static}/**/*`, dest: PATH.dist }
]
}),
@@ -141,45 +128,39 @@ export default defineConfig({
plg_svg2svg(
[
{ src: PATH.src + "/svg/empty_favicon.svg", dest: PATH.dist + "/img/empty_favicon.svg" },
{ src: PATH.src + "/svg/select-dark.svg", dest: PATH.dist + "/img/select-dark.svg" },
{ src: PATH.src + "/svg/select-light.svg", dest: PATH.dist + "/img/select-light.svg" },
],
svg2svg_opts,
),
// SearXNG brand (static)
plg_svg2png(
[
{ src: PATH.brand + "/searxng-wordmark.svg", dest: PATH.dist + "/img/favicon.png" },
{ src: PATH.brand + "/searxng.svg", dest: PATH.dist + "/img/searxng.png" },
],
),
// -- svg
plg_svg2svg(
[
{ src: PATH.brand + "/searxng.svg", dest: PATH.dist + "/img/searxng.svg" },
{ src: PATH.brand + "/img_load_error.svg", dest: PATH.dist + "/img/img_load_error.svg" },
],
svg2svg_opts,
),
// -- favicon
plg_svg2svg(
[ { src: PATH.brand + "/searxng-wordmark.svg", dest: PATH.dist + "/img/favicon.svg" } ],
svg2svg_favicon_opts,
),
// -- simple templates
plg_svg2svg(
[
{ src: PATH.brand + "/searxng-wordmark.svg", dest: PATH.templates + "/searxng-wordmark.min.svg" },
{ src: `${PATH.src}/svg/empty_favicon.svg`, dest: `${PATH.dist}/img/empty_favicon.svg` },
{ src: `${PATH.src}/svg/select-dark.svg`, dest: `${PATH.dist}/img/select-dark.svg` },
{ src: `${PATH.src}/svg/select-light.svg`, dest: `${PATH.dist}/img/select-light.svg` }
],
svg2svg_opts
),
] // end: plugins
// SearXNG brand (static)
plg_svg2png([
{ src: `${PATH.brand}/searxng-wordmark.svg`, dest: `${PATH.dist}/img/favicon.png` },
{ src: `${PATH.brand}/searxng.svg`, dest: `${PATH.dist}/img/searxng.png` }
]),
// -- svg
plg_svg2svg(
[
{ src: `${PATH.brand}/searxng.svg`, dest: `${PATH.dist}/img/searxng.svg` },
{ src: `${PATH.brand}/img_load_error.svg`, dest: `${PATH.dist}/img/img_load_error.svg` }
],
svg2svg_opts
),
// -- favicon
plg_svg2svg(
[{ src: `${PATH.brand}/searxng-wordmark.svg`, dest: `${PATH.dist}/img/favicon.svg` }],
svg2svg_favicon_opts
),
// -- simple templates
plg_svg2svg(
[{ src: `${PATH.brand}/searxng-wordmark.svg`, dest: `${PATH.templates}/searxng-wordmark.min.svg` }],
svg2svg_opts
)
] // end: plugins
});

View File

@@ -6,8 +6,8 @@ Development Quickstart
.. _npm: https://www.npmjs.com/
.. _Node.js: https://nodejs.org/
.. _eslint: https://eslint.org/
.. _stylelint: https://stylelint.io/
.. _Biome: https://biomejs.dev/
.. _Stylelint: https://stylelint.io/
.. sidebar:: further read
@@ -41,8 +41,8 @@ to our ":ref:`how to contribute`" guideline.
If you implement themes, you will need to setup a :ref:`Node.js environment
<make node.env>`. Before you call *make run* (2.), you need to compile the
modified styles and JavaScript: ``make node.clean themes.all``. If eslint_ or
stylelint_ report some issues, try ``make themes.fix``.
modified styles and JavaScript: ``make node.clean themes.all``. If Biome_
or Stylelint_ reports issues, try ``make themes.fix``.
Alternatively you can also compile selective the theme you have modified,
e.g. the *simple* theme.

View File

@@ -1,6 +1,5 @@
{
"dependencies": {
"eslint": "^9.17.0",
"pyright": "^1.1.391"
},
"scripts": {

View File

@@ -1,2 +1,2 @@
(function(n,t){var r=t.currentScript||function(){var s=t.getElementsByTagName("script");return s[s.length-1]}();n.searxng={settings:JSON.parse(atob(r.getAttribute("client_settings")))};var e=t.getElementsByTagName("html")[0];e.classList.remove("no-js"),e.classList.add("js")})(window,document);
((n,t)=>{const c=()=>{const s=t.getElementsByTagName("script");return s[s.length-1]},r=t.currentScript||c();n.searxng={settings:JSON.parse(atob(r.getAttribute("client_settings")))};const e=t.getElementsByTagName("html")[0];e.classList.remove("no-js"),e.classList.add("js")})(window,document);
//# sourceMappingURL=searxng.head.min.js.map

View File

@@ -1 +1 @@
{"version":3,"file":"searxng.head.min.js","sources":["../../../../../client/simple/src/js/head/00_init.js"],"sourcesContent":["/* SPDX-License-Identifier: AGPL-3.0-or-later */\n(function (w, d) {\n 'use strict';\n\n // add data- properties\n var script = d.currentScript || (function () {\n var scripts = d.getElementsByTagName('script');\n return scripts[scripts.length - 1];\n })();\n\n w.searxng = {\n settings: JSON.parse(atob(script.getAttribute('client_settings')))\n };\n\n // update the css\n var htmlElement = d.getElementsByTagName(\"html\")[0];\n htmlElement.classList.remove('no-js');\n htmlElement.classList.add('js');\n\n})(window, document);\n"],"names":["w","d","script","scripts","htmlElement"],"mappings":"CACC,SAAUA,EAAGC,EAAG,CAIf,IAAIC,EAASD,EAAE,eAAmB,UAAY,CAC5C,IAAIE,EAAUF,EAAE,qBAAqB,QAAQ,EAC7C,OAAOE,EAAQA,EAAQ,OAAS,CAAC,CACrC,EAAM,EAEJH,EAAE,QAAU,CACV,SAAU,KAAK,MAAM,KAAKE,EAAO,aAAa,iBAAiB,CAAC,CAAC,CAClE,EAGD,IAAIE,EAAcH,EAAE,qBAAqB,MAAM,EAAE,CAAC,EAClDG,EAAY,UAAU,OAAO,OAAO,EACpCA,EAAY,UAAU,IAAI,IAAI,CAEhC,GAAG,OAAQ,QAAQ"}
{"version":3,"file":"searxng.head.min.js","sources":["../../../../../client/simple/src/js/head/00_init.js"],"sourcesContent":["/* SPDX-License-Identifier: AGPL-3.0-or-later */\n((w, d) => {\n // add data- properties\n const getLastScriptElement = () => {\n const scripts = d.getElementsByTagName(\"script\");\n return scripts[scripts.length - 1];\n };\n\n const script = d.currentScript || getLastScriptElement();\n\n w.searxng = {\n settings: JSON.parse(atob(script.getAttribute(\"client_settings\")))\n };\n\n // update the css\n const htmlElement = d.getElementsByTagName(\"html\")[0];\n htmlElement.classList.remove(\"no-js\");\n htmlElement.classList.add(\"js\");\n})(window, document);\n"],"names":["w","d","getLastScriptElement","scripts","script","htmlElement"],"mappings":"CACC,CAACA,EAAGC,IAAM,CAET,MAAMC,EAAuB,IAAM,CACjC,MAAMC,EAAUF,EAAE,qBAAqB,QAAQ,EAC/C,OAAOE,EAAQA,EAAQ,OAAS,CAAC,CACrC,EAEQC,EAASH,EAAE,eAAiBC,EAAoB,EAEtDF,EAAE,QAAU,CACV,SAAU,KAAK,MAAM,KAAKI,EAAO,aAAa,iBAAiB,CAAC,CAAC,CACrE,EAGE,MAAMC,EAAcJ,EAAE,qBAAqB,MAAM,EAAE,CAAC,EACpDI,EAAY,UAAU,OAAO,OAAO,EACpCA,EAAY,UAAU,IAAI,IAAI,CAChC,GAAG,OAAQ,QAAQ"}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -98,7 +98,8 @@ static.build.commit() {
( set -e
# fix & build the themes
themes.fix
themes.fix
themes.lint
themes.all
# add build files

View File

@@ -5,14 +5,23 @@ themes.help(){
cat <<EOF
themes.:
all : test & build all themes
simple : test & build simple theme
lint : lint JS & CSS (LESS) files
fix : fix JS & CSS (LESS) files
test : test all themes
fix : fix JS & CSS (LESS)
EOF
}
themes.all() {
( set -e
build_msg SIMPLE "theme: run build"
vite.simple.build
)
dump_return $?
}
themes.simple() {
( set -e
build_msg SIMPLE "theme: run build (simple)"
vite.simple.build
)
dump_return $?
@@ -20,12 +29,20 @@ themes.all() {
themes.fix() {
( set -e
build_msg SIMPLE "theme: fix"
build_msg SIMPLE "theme: fix (all themes)"
vite.simple.fix
)
dump_return $?
}
themes.lint() {
( set -e
build_msg SIMPLE "theme: lint (all themes)"
vite.simple.lint
)
dump_return $?
}
themes.test() {
( set -e
# we run a build to test (in CI)

View File

@@ -9,6 +9,8 @@ vite.help(){
vite.: .. to be done ..
simple.:
build: build static files of the simple theme
fix: run prettiers on simple theme
lint: run linters on simple theme
dev: start development server
EOF
}
@@ -36,8 +38,6 @@ vite.simple.build() {
pushd "${VITE_SIMPLE_THEME}"
npm install
npm run fix
npm run icons.html
npm run build
popd &> /dev/null
)
@@ -50,6 +50,13 @@ vite.simple.fix() {
)
}
vite.simple.lint() {
( set -e
node.env
npm --prefix client/simple run lint
)
}
templates.simple.pygments() {
build_msg PYGMENTS "searxng_extra/update/update_pygments.py"
pyenv.cmd python searxng_extra/update/update_pygments.py \