Merge pull request #634 from not-my-profile/powered-by

Introduce `categories_as_tabs` & group engines in tabs
This commit is contained in:
Alexandre Flament 2022-01-06 09:22:02 +01:00 committed by GitHub
commit aedd6279b3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
44 changed files with 247 additions and 105 deletions

View file

@ -16,11 +16,18 @@ Explanation of the :ref:`general engine configuration` shown in the table
SearXNG supports {{engines | length}} search engines (of which {{enabled_engine_count}} are enabled by default). SearXNG supports {{engines | length}} search engines (of which {{enabled_engine_count}} are enabled by default).
{% for category, engines in engines.items() | groupby('1.categories.0') %} {% for category, engines in categories_as_tabs.items() %}
{{category}} search engines {{category}} search engines
--------------------------------------- ---------------------------------------
{% for group, engines in engines | group_engines_in_tab %}
{% if loop.length > 1 %}
{{group}}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
{% endif %}
.. flat-table:: .. flat-table::
:header-rows: 2 :header-rows: 2
:stub-columns: 1 :stub-columns: 1
@ -39,9 +46,9 @@ Explanation of the :ref:`general engine configuration` shown in the table
- Safe search - Safe search
- Time range - Time range
{% for name, mod in engines | sort_engines %} {% for mod in engines %}
* - `{{name}} <{{mod.about and mod.about.website}}>`_ * - `{{mod.name}} <{{mod.about and mod.about.website}}>`_
- ``!{{mod.shortcut}}`` - ``!{{mod.shortcut}}``
- {%- if 'searx.engines.' + mod.__name__ in documented_modules %} - {%- if 'searx.engines.' + mod.__name__ in documented_modules %}
:py:mod:`~searx.engines.{{mod.__name__}}` :py:mod:`~searx.engines.{{mod.__name__}}`
@ -65,3 +72,4 @@ Explanation of the :ref:`general engine configuration` shown in the table
{% endfor %} {% endfor %}
{% endfor %} {% endfor %}
{% endfor %}

View file

@ -222,6 +222,26 @@ Communication with search engines.
``max_redirects`` : ``max_redirects`` :
30 by default. Maximum redirect before it is an error. 30 by default. Maximum redirect before it is an error.
``categories_as_tabs:``
-----------------------
A list of the categories that are displayed as tabs in the user interface.
Categories not listed here can still be searched with the :ref:`search-syntax`.
.. code-block:: yaml
categories_as_tabs:
general:
images:
videos:
news:
map:
music:
it:
science:
files:
social media:
.. _settings engine: .. _settings engine:
Engine settings Engine settings

View file

@ -39,7 +39,9 @@ exclude_patterns = ['build-templates/*.rst']
import searx.engines import searx.engines
import searx.plugins import searx.plugins
import searx.webutils
searx.engines.load_engines(searx.settings['engines']) searx.engines.load_engines(searx.settings['engines'])
jinja_contexts = { jinja_contexts = {
'searx': { 'searx': {
'engines': searx.engines.engines, 'engines': searx.engines.engines,
@ -48,14 +50,12 @@ jinja_contexts = {
'node': os.getenv('NODE_MINIMUM_VERSION') 'node': os.getenv('NODE_MINIMUM_VERSION')
}, },
'enabled_engine_count': sum(not x.disabled for x in searx.engines.engines.values()), 'enabled_engine_count': sum(not x.disabled for x in searx.engines.engines.values()),
'categories': searx.engines.categories,
'categories_as_tabs': {c: searx.engines.categories[c] for c in searx.settings['categories_as_tabs']},
}, },
} }
jinja_filters = { jinja_filters = {
'sort_engines': 'group_engines_in_tab': searx.webutils.group_engines_in_tab,
lambda engines: sorted(
engines,
key=lambda engine: (engine[1].disabled, engine[1].about.get('language', ''), engine[0])
)
} }
# Let the Jinja template in configured_engines.rst access documented_modules # Let the Jinja template in configured_engines.rst access documented_modules

View file

@ -13,6 +13,7 @@ usage::
import sys import sys
import copy import copy
from typing import List
from os.path import realpath, dirname from os.path import realpath, dirname
from babel.localedata import locale_identifiers from babel.localedata import locale_identifiers
@ -44,7 +45,29 @@ ENGINE_DEFAULT_ARGS = {
"display_error_messages": True, "display_error_messages": True,
"tokens": [], "tokens": [],
} }
"""Defaults for the namespace of an engine module, see :py:func:`load_engine`""" # set automatically when an engine does not have any tab category
OTHER_CATEGORY = 'other'
class Engine: # pylint: disable=too-few-public-methods
"""This class is currently never initialized and only used for type hinting."""
name: str
engine: str
shortcut: str
categories: List[str]
supported_languages: List[str]
about: dict
inactive: bool
disabled: bool
language_support: bool
paging: bool
safesearch: bool
time_range_support: bool
timeout: float
# Defaults for the namespace of an engine module, see :py:func:`load_engine``
categories = {'general': []} categories = {'general': []}
engines = {} engines = {}
@ -113,6 +136,9 @@ def load_engine(engine_data):
set_loggers(engine, engine_name) set_loggers(engine, engine_name)
if not any(cat in settings['categories_as_tabs'] for cat in engine.categories):
engine.categories.append(OTHER_CATEGORY)
return engine return engine
@ -138,6 +164,8 @@ def update_engine_attributes(engine, engine_data):
if isinstance(param_value, str): if isinstance(param_value, str):
param_value = list(map(str.strip, param_value.split(','))) param_value = list(map(str.strip, param_value.split(',')))
engine.categories = param_value engine.categories = param_value
elif hasattr(engine, 'about') and param_name == 'about':
engine.about = {**engine.about, **engine_data['about']}
else: else:
setattr(engine, param_name, param_value) setattr(engine, param_name, param_value)

View file

@ -24,7 +24,7 @@ about = {
} }
# engine dependent config # engine dependent config
categories = ['files'] categories = ['files', 'apps']
paging = True paging = True
time_range_support = False time_range_support = False

View file

@ -20,7 +20,7 @@ about = {
} }
# engine dependent config # engine dependent config
categories = ['it'] categories = ['it', 'software wikis']
paging = True paging = True
base_url = 'https://wiki.archlinux.org' base_url = 'https://wiki.archlinux.org'

View file

@ -20,7 +20,7 @@ about = {
} }
# engine dependent config # engine dependent config
categories = ['general'] categories = ['general', 'web']
paging = True paging = True
time_range_support = False time_range_support = False
safesearch = False safesearch = False

View file

@ -27,7 +27,7 @@ about = {
} }
# engine dependent config # engine dependent config
categories = ['images'] categories = ['images', 'web']
paging = True paging = True
safesearch = True safesearch = True
time_range_support = True time_range_support = True

View file

@ -26,7 +26,7 @@ about = {
"results": 'HTML', "results": 'HTML',
} }
categories = ['videos'] categories = ['videos', 'web']
paging = True paging = True
safesearch = True safesearch = True
time_range_support = True time_range_support = True

View file

@ -27,7 +27,7 @@ about = {
} }
# engine dependent config # engine dependent config
categories = ['general'] categories = ['general', 'web']
paging = True paging = True
supported_languages_url = 'https://duckduckgo.com/util/u588.js' supported_languages_url = 'https://duckduckgo.com/util/u588.js'
time_range_support = True time_range_support = True

View file

@ -27,7 +27,7 @@ about = {
} }
# engine dependent config # engine dependent config
categories = ['images'] categories = ['images', 'web']
paging = True paging = True
safesearch = True safesearch = True

View file

@ -19,7 +19,7 @@ about = {
"language": 'de', "language": 'de',
} }
categories = ['general'] categories = ['dictionaries']
paging = True paging = True
# search-url # search-url

View file

@ -17,7 +17,7 @@ about = {
"results": 'HTML', "results": 'HTML',
} }
categories = ['general'] categories = ['general', 'web']
paging = False paging = False
safesearch = True safesearch = True

View file

@ -18,7 +18,7 @@ about = {
} }
# engine dependent config # engine dependent config
categories = ['files'] categories = ['files', 'apps']
paging = True paging = True
# search-url # search-url

View file

@ -20,7 +20,7 @@ about = {
} }
# engine dependent config # engine dependent config
categories = ['music'] categories = ['music', 'lyrics']
paging = True paging = True
page_size = 5 page_size = 5

View file

@ -18,7 +18,7 @@ about = {
} }
# engine dependent config # engine dependent config
categories = ['it'] categories = ['it', 'software wikis']
paging = True paging = True
base_url = 'https://wiki.gentoo.org' base_url = 'https://wiki.gentoo.org'

View file

@ -22,7 +22,7 @@ about = {
} }
# engine dependent config # engine dependent config
categories = ['general'] categories = ['general', 'web']
# gigablast's pagination is totally damaged, don't use it # gigablast's pagination is totally damaged, don't use it
paging = False paging = False
safesearch = True safesearch = True

View file

@ -17,7 +17,7 @@ about = {
} }
# engine dependent config # engine dependent config
categories = ['it'] categories = ['it', 'repos']
# search-url # search-url
search_url = 'https://api.github.com/search/repositories?sort=stars&order=desc&{query}' # noqa search_url = 'https://api.github.com/search/repositories?sort=stars&order=desc&{query}' # noqa

View file

@ -41,7 +41,7 @@ about = {
} }
# engine dependent config # engine dependent config
categories = ['general'] categories = ['general', 'web']
paging = True paging = True
time_range_support = True time_range_support = True
safesearch = True safesearch = True

View file

@ -45,7 +45,7 @@ about = {
} }
# engine dependent config # engine dependent config
categories = ['images'] categories = ['images', 'web']
paging = False paging = False
use_locale_domain = True use_locale_domain = True
time_range_support = True time_range_support = True

View file

@ -54,7 +54,7 @@ about = {
# engine dependent config # engine dependent config
categories = ['videos'] categories = ['videos', 'web']
paging = False paging = False
language_support = True language_support = True
use_locale_domain = True use_locale_domain = True

View file

@ -27,9 +27,7 @@ about = {
"results": 'HTML', "results": 'HTML',
} }
categories = [ categories = []
'general',
]
paging = False paging = False
# suggestion_url = "https://sg.media-imdb.com/suggestion/{letter}/{query}.json" # suggestion_url = "https://sg.media-imdb.com/suggestion/{letter}/{query}.json"

View file

@ -25,6 +25,7 @@ about = {
"language": "cz", "language": "cz",
} }
categories = ['general', 'web']
base_url = 'https://search.seznam.cz/' base_url = 'https://search.seznam.cz/'

View file

@ -21,7 +21,7 @@ about = {
"language": 'pl', "language": 'pl',
} }
categories = ['general'] categories = ['dictionaries']
paging = False paging = False
URL = 'https://sjp.pwn.pl' URL = 'https://sjp.pwn.pl'

View file

@ -23,7 +23,7 @@ about = {
} }
# engine dependent config # engine dependent config
categories = ['general'] categories = ['general', 'web']
# there is a mechanism to block "bot" search # there is a mechanism to block "bot" search
# (probably the parameter qid), require # (probably the parameter qid), require
# storing of qid's between mulitble search-calls # storing of qid's between mulitble search-calls

View file

@ -14,7 +14,7 @@ about = {
} }
engine_type = 'online_dictionary' engine_type = 'online_dictionary'
categories = ['general'] categories = ['dictionaries']
url = 'https://api.mymemory.translated.net/get?q={query}&langpair={from_lang}|{to_lang}{key}' url = 'https://api.mymemory.translated.net/get?q={query}&langpair={from_lang}|{to_lang}{key}'
web_url = 'https://mymemory.translated.net/en/{from_lang}/{to_lang}/{query}' web_url = 'https://mymemory.translated.net/en/{from_lang}/{to_lang}/{query}'
weight = 100 weight = 100

View file

@ -31,7 +31,7 @@ about = {
} }
# engine dependent config # engine dependent config
categories = ['general'] categories = ['general', 'web']
paging = True paging = True
time_range_support = True time_range_support = True
supported_languages_url = 'https://search.yahoo.com/preferences/languages' supported_languages_url = 'https://search.yahoo.com/preferences/languages'

View file

@ -12,6 +12,7 @@ from urllib.parse import parse_qs, urlencode
from searx import settings, autocomplete from searx import settings, autocomplete
from searx.locales import LOCALE_NAMES from searx.locales import LOCALE_NAMES
from searx.webutils import VALID_LANGUAGE_CODE from searx.webutils import VALID_LANGUAGE_CODE
from searx.engines import OTHER_CATEGORY
COOKIE_MAX_AGE = 60 * 60 * 24 * 365 * 5 # 5 years COOKIE_MAX_AGE = 60 * 60 * 24 * 365 * 5 # 5 years
@ -271,6 +272,8 @@ class EnginesSetting(SwitchableSetting):
transformed_choices = [] transformed_choices = []
for engine_name, engine in self.choices.items(): # pylint: disable=no-member,access-member-before-definition for engine_name, engine in self.choices.items(): # pylint: disable=no-member,access-member-before-definition
for category in engine.categories: for category in engine.categories:
if not category in list(settings['categories_as_tabs'].keys()) + [OTHER_CATEGORY]:
continue
transformed_choice = {} transformed_choice = {}
transformed_choice['default_on'] = not engine.disabled transformed_choice['default_on'] = not engine.disabled
transformed_choice['id'] = '{}__{}'.format(engine_name, category) transformed_choice['id'] = '{}__{}'.format(engine_name, category)

View file

@ -222,7 +222,7 @@ class BangParser(QueryPartParser):
# check if query starts with categorie name # check if query starts with categorie name
for category in categories: for category in categories:
if category.startswith(value): if category.startswith(value):
self._add_autocomplete(first_char + category) self._add_autocomplete(first_char + category.replace(' ', '_'))
# check if query starts with engine name # check if query starts with engine name
for engine in engines: for engine in engines:

View file

@ -82,12 +82,6 @@ ui:
simple_style: auto simple_style: auto
# Open result links in a new tab by default # Open result links in a new tab by default
# results_on_new_tab: false # results_on_new_tab: false
# categories_order :
# - general
# - files
# - map
# - it
# - science
# Lock arbitrary settings on the preferences page. To find the ID of the user # Lock arbitrary settings on the preferences page. To find the ID of the user
# setting you want to lock, check the ID of the form on the page "preferences". # setting you want to lock, check the ID of the form on the page "preferences".
@ -234,6 +228,18 @@ checker:
result_container: result_container:
- has_infobox - has_infobox
categories_as_tabs:
general:
images:
videos:
news:
map:
music:
it:
science:
files:
social media:
engines: engines:
- name: apk mirror - name: apk mirror
engine: apkmirror engine: apkmirror
@ -320,7 +326,7 @@ engines:
url_xpath: //article[@class="repo-summary"]//a[@class="repo-link"]/@href url_xpath: //article[@class="repo-summary"]//a[@class="repo-link"]/@href
title_xpath: //article[@class="repo-summary"]//a[@class="repo-link"] title_xpath: //article[@class="repo-summary"]//a[@class="repo-link"]
content_xpath: //article[@class="repo-summary"]/p content_xpath: //article[@class="repo-summary"]/p
categories: it categories: [it, repos]
timeout: 4.0 timeout: 4.0
disabled: true disabled: true
shortcut: bb shortcut: bb
@ -419,7 +425,7 @@ engines:
- name: docker hub - name: docker hub
engine: docker_hub engine: docker_hub
shortcut: dh shortcut: dh
categories: it categories: [it, packages]
- name: erowid - name: erowid
engine: xpath engine: xpath
@ -430,7 +436,7 @@ engines:
url_xpath: //dl[@class="results-list"]/dt[@class="result-title"]/a/@href url_xpath: //dl[@class="results-list"]/dt[@class="result-title"]/a/@href
title_xpath: //dl[@class="results-list"]/dt[@class="result-title"]/a/text() title_xpath: //dl[@class="results-list"]/dt[@class="result-title"]/a/text()
content_xpath: //dl[@class="results-list"]/dd[@class="result-details"] content_xpath: //dl[@class="results-list"]/dd[@class="result-details"]
categories: general categories: []
shortcut: ew shortcut: ew
disabled: true disabled: true
about: about:
@ -489,7 +495,8 @@ engines:
content_xpath: //section[contains(@class, "word__defination")] content_xpath: //section[contains(@class, "word__defination")]
first_page_num: 1 first_page_num: 1
shortcut: et shortcut: et
disabled: true categories: [dictionaries]
disabled: false
about: about:
website: https://www.etymonline.com/ website: https://www.etymonline.com/
wikidata_id: Q1188617 wikidata_id: Q1188617
@ -528,7 +535,7 @@ engines:
- name: free software directory - name: free software directory
engine: mediawiki engine: mediawiki
shortcut: fsd shortcut: fsd
categories: it categories: [it, software wikis]
base_url: https://directory.fsf.org/ base_url: https://directory.fsf.org/
number_of_results: 5 number_of_results: 5
# what part of a page matches the query string: title, text, nearmatch # what part of a page matches the query string: title, text, nearmatch
@ -579,7 +586,7 @@ engines:
title_query: name_with_namespace title_query: name_with_namespace
content_query: description content_query: description
page_size: 20 page_size: 20
categories: it categories: [it, repos]
shortcut: gl shortcut: gl
timeout: 10.0 timeout: 10.0
disabled: true disabled: true
@ -605,7 +612,7 @@ engines:
url_query: html_url url_query: html_url
title_query: name title_query: name
content_query: description content_query: description
categories: it categories: [it, repos]
shortcut: cb shortcut: cb
disabled: true disabled: true
about: about:
@ -671,7 +678,7 @@ engines:
url_xpath: './/div[@class="RZEgze"]//div[@class="kCSSQe"]//a/@href' url_xpath: './/div[@class="RZEgze"]//div[@class="kCSSQe"]//a/@href'
content_xpath: './/div[@class="RZEgze"]//a[@class="mnKHRc"]' content_xpath: './/div[@class="RZEgze"]//a[@class="mnKHRc"]'
thumbnail_xpath: './/div[@class="uzcko"]/div/span[1]//img/@data-src' thumbnail_xpath: './/div[@class="uzcko"]/div/span[1]//img/@data-src'
categories: files categories: [files, apps]
shortcut: gpa shortcut: gpa
disabled: true disabled: true
about: about:
@ -749,7 +756,7 @@ engines:
url_xpath: './/div[@class="ans"]//a/@href' url_xpath: './/div[@class="ans"]//a/@href'
content_xpath: './/div[@class="from"]' content_xpath: './/div[@class="from"]'
page_size: 20 page_size: 20
categories: it categories: [it, packages]
shortcut: ho shortcut: ho
about: about:
website: https://hoogle.haskell.org/ website: https://hoogle.haskell.org/
@ -844,7 +851,7 @@ engines:
engine: xpath engine: xpath
timeout: 4.0 timeout: 4.0
disabled: true disabled: true
categories: music categories: [music, lyrics]
paging: true paging: true
search_url: https://search.azlyrics.com/search.php?q={query}&w=lyrics&p={pageno} search_url: https://search.azlyrics.com/search.php?q={query}&w=lyrics&p={pageno}
url_xpath: //td[@class="text-left visitedlyr"]/a/@href url_xpath: //td[@class="text-left visitedlyr"]/a/@href
@ -899,7 +906,7 @@ engines:
title_query: package/name title_query: package/name
content_query: package/description content_query: package/description
page_size: 25 page_size: 25
categories: it categories: [it, packages]
disabled: true disabled: true
timeout: 5.0 timeout: 5.0
shortcut: npm shortcut: npm
@ -1008,7 +1015,7 @@ engines:
url_query: url url_query: url
title_query: name title_query: name
content_query: description content_query: description
categories: it categories: [it, packages]
disabled: true disabled: true
timeout: 5.0 timeout: 5.0
shortcut: pack shortcut: pack
@ -1065,7 +1072,7 @@ engines:
content_xpath: ./p content_xpath: ./p
suggestion_xpath: /html/body/main/div/div/div/form/div/div[@class="callout-block"]/p/span/a[@class="link"] suggestion_xpath: /html/body/main/div/div/div/form/div/div[@class="callout-block"]/p/span/a[@class="link"]
first_page_num: 1 first_page_num: 1
categories: it categories: [it, packages]
about: about:
website: https://pypi.org website: https://pypi.org
wikidata_id: Q2984686 wikidata_id: Q2984686
@ -1077,7 +1084,7 @@ engines:
- name: qwant - name: qwant
engine: qwant engine: qwant
shortcut: qw shortcut: qw
categories: general categories: [general, web]
disabled: false disabled: false
additional_tests: additional_tests:
rosebud: *test_rosebud rosebud: *test_rosebud
@ -1092,14 +1099,14 @@ engines:
- name: qwant images - name: qwant images
engine: qwant engine: qwant
shortcut: qwi shortcut: qwi
categories: images categories: [images, web]
disabled: false disabled: false
network: qwant network: qwant
- name: qwant videos - name: qwant videos
engine: qwant engine: qwant
shortcut: qwv shortcut: qwv
categories: videos categories: [videos, web]
disabled: false disabled: false
network: qwant network: qwant
@ -1159,19 +1166,19 @@ engines:
engine: stackexchange engine: stackexchange
shortcut: st shortcut: st
api_site: 'stackoverflow' api_site: 'stackoverflow'
categories: it categories: [it, q&a]
- name: askubuntu - name: askubuntu
engine: stackexchange engine: stackexchange
shortcut: ubuntu shortcut: ubuntu
api_site: 'askubuntu' api_site: 'askubuntu'
categories: it categories: [it, q&a]
- name: superuser - name: superuser
engine: stackexchange engine: stackexchange
shortcut: su shortcut: su
api_site: 'superuser' api_site: 'superuser'
categories: it categories: [it, q&a]
- name: searchcode code - name: searchcode code
engine: searchcode_code engine: searchcode_code
@ -1354,7 +1361,7 @@ engines:
url_query: URL url_query: URL
title_query: Title title_query: Title
content_query: Snippet content_query: Snippet
categories: general categories: [general, web]
shortcut: wib shortcut: wib
disabled: true disabled: true
about: about:
@ -1413,11 +1420,11 @@ engines:
- name: wiktionary - name: wiktionary
engine: mediawiki engine: mediawiki
shortcut: wt shortcut: wt
categories: general categories: [dictionaries]
base_url: "https://{language}.wiktionary.org/" base_url: "https://{language}.wiktionary.org/"
number_of_results: 5 number_of_results: 5
search_type: text search_type: text
disabled: true disabled: false
about: about:
website: https://www.wiktionary.org/ website: https://www.wiktionary.org/
wikidata_id: Q151 wikidata_id: Q151
@ -1467,7 +1474,7 @@ engines:
engine: translated engine: translated
shortcut: tl shortcut: tl
timeout: 5.0 timeout: 5.0
disabled: true disabled: false
# You can use without an API key, but you are limited to 1000 words/day # You can use without an API key, but you are limited to 1000 words/day
# See: https://mymemory.translated.net/doc/usagelimits.php # See: https://mymemory.translated.net/doc/usagelimits.php
# api_key: '' # api_key: ''
@ -1501,6 +1508,7 @@ engines:
shortcut: mjk shortcut: mjk
engine: xpath engine: xpath
paging: true paging: true
categories: [general, web]
search_url: https://www.mojeek.com/search?q={query}&s={pageno} search_url: https://www.mojeek.com/search?q={query}&s={pageno}
results_xpath: /html/body//div[@class="results"]/ul[@class="results-standard"]/li results_xpath: /html/body//div[@class="results"]/ul[@class="results-standard"]/li
url_xpath: ./h2/a/@href url_xpath: ./h2/a/@href
@ -1520,6 +1528,7 @@ engines:
- name: naver - name: naver
shortcut: nvr shortcut: nvr
categories: [general, web]
engine: xpath engine: xpath
paging: true paging: true
search_url: https://search.naver.com/search.naver?where=webkr&sm=osp_hty&ie=UTF-8&query={query}&start={pageno} search_url: https://search.naver.com/search.naver?where=webkr&sm=osp_hty&ie=UTF-8&query={query}&start={pageno}
@ -1549,7 +1558,7 @@ engines:
content_xpath: ./span/p content_xpath: ./span/p
suggestion_xpath: /html/body/main/div/div[@class="search__suggestions"]/p/a suggestion_xpath: /html/body/main/div/div[@class="search__suggestions"]/p/a
first_page_num: 1 first_page_num: 1
categories: it categories: [it, packages]
disabled: true disabled: true
about: about:
website: https://rubygems.org/ website: https://rubygems.org/
@ -1593,14 +1602,14 @@ engines:
engine: wordnik engine: wordnik
shortcut: def shortcut: def
base_url: https://www.wordnik.com/ base_url: https://www.wordnik.com/
categories: general categories: [dictionaries]
timeout: 5.0 timeout: 5.0
disabled: true disabled: false
- name: woxikon.de synonyme - name: woxikon.de synonyme
engine: xpath engine: xpath
shortcut: woxi shortcut: woxi
categories: general categories: [dictionaries]
timeout: 5.0 timeout: 5.0
disabled: true disabled: true
search_url: https://synonyme.woxikon.de/synonyme/{query}.php search_url: https://synonyme.woxikon.de/synonyme/{query}.php
@ -1619,7 +1628,6 @@ engines:
engine: sjp engine: sjp
shortcut: sjp shortcut: sjp
base_url: https://sjp.pwn.pl/ base_url: https://sjp.pwn.pl/
categories: general
timeout: 5.0 timeout: 5.0
disabled: true disabled: true
@ -1652,7 +1660,7 @@ engines:
title_xpath: //span[@class="snippet-title"] title_xpath: //span[@class="snippet-title"]
content_xpath: //p[1][@class="snippet-description"] content_xpath: //p[1][@class="snippet-description"]
suggestion_xpath: //div[@class="text-gray h6"]/a suggestion_xpath: //div[@class="text-gray h6"]/a
categories: general categories: [general, web]
about: about:
website: https://brave.com/search/ website: https://brave.com/search/
wikidata_id: Q107355971 wikidata_id: Q107355971

View file

@ -20,18 +20,18 @@ OUTPUT_FORMATS = ['html', 'csv', 'json', 'rss']
LANGUAGE_CODES = ['all'] + list(l[0] for l in languages) LANGUAGE_CODES = ['all'] + list(l[0] for l in languages)
OSCAR_STYLE = ('logicodev', 'logicodev-dark', 'pointhi') OSCAR_STYLE = ('logicodev', 'logicodev-dark', 'pointhi')
SIMPLE_STYLE = ('auto', 'light', 'dark') SIMPLE_STYLE = ('auto', 'light', 'dark')
CATEGORY_ORDER = [ CATEGORIES_AS_TABS = {
'general', 'general': {},
'images', 'images': {},
'videos', 'videos': {},
'news', 'news': {},
'map', 'map': {},
'music', 'music': {},
'it', 'it': {},
'science', 'science': {},
'files', 'files': {},
'social media', 'social media': {},
] }
STR_TO_BOOL = { STR_TO_BOOL = {
'0': False, '0': False,
'false': False, 'false': False,
@ -182,7 +182,6 @@ SCHEMA = {
'results_on_new_tab': SettingsValue(bool, False), 'results_on_new_tab': SettingsValue(bool, False),
'advanced_search': SettingsValue(bool, False), 'advanced_search': SettingsValue(bool, False),
'query_in_title': SettingsValue(bool, False), 'query_in_title': SettingsValue(bool, False),
'categories_order': SettingsValue(list, CATEGORY_ORDER),
}, },
'preferences': { 'preferences': {
'lock': SettingsValue(list, []), 'lock': SettingsValue(list, []),
@ -213,6 +212,7 @@ SCHEMA = {
'checker': { 'checker': {
'off_when_debug': SettingsValue(bool, True), 'off_when_debug': SettingsValue(bool, True),
}, },
'categories_as_tabs': SettingsValue(dict, CATEGORIES_AS_TABS),
'engines': SettingsValue(list, []), 'engines': SettingsValue(list, []),
'doi_resolvers': {}, 'doi_resolvers': {},
} }

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -72,6 +72,7 @@
/// Settings Colors /// Settings Colors
--color-settings-tr-hover: #f7f7f7; --color-settings-tr-hover: #f7f7f7;
--color-settings-engine-description-font: darken(#dcdcdc, 30%); --color-settings-engine-description-font: darken(#dcdcdc, 30%);
--color-settings-engine-group-background: #0001;
/// Detail modal /// Detail modal
--color-result-detail-font: #fff; --color-result-detail-font: #fff;
--color-result-detail-label-font: lightgray; --color-result-detail-label-font: lightgray;
@ -180,6 +181,7 @@
/// Settings Colors /// Settings Colors
--color-settings-tr-hover: #2d2d2d; --color-settings-tr-hover: #2d2d2d;
--color-settings-engine-description-font: darken(#dcdcdc, 30%); --color-settings-engine-description-font: darken(#dcdcdc, 30%);
--color-settings-engine-group-background: #1a1919;
/// Toolkit Colors /// Toolkit Colors
--color-toolkit-badge-font: #fff; --color-toolkit-badge-font: #fff;
--color-toolkit-badge-background: #777; --color-toolkit-badge-background: #777;

View file

@ -161,6 +161,12 @@
} }
} }
} }
.engine-group {
text-align: left;
font-weight: normal;
background: var(--color-settings-engine-group-background);
}
} }
@media screen and (max-width: @tablet) { @media screen and (max-width: @tablet) {

View file

@ -1,11 +1,11 @@
<div id="categories"> <div id="categories">
{%- if rtl -%} {%- if rtl -%}
{% for category in categories | reverse -%} {% for category in categories_as_tabs | reverse -%}
<input class="hidden" type="checkbox" id="checkbox_{{ category|replace(' ', '_') }}" name="category_{{ category }}" {% if category in selected_categories %}checked="checked"{% endif %} />{{- '' -}} <input class="hidden" type="checkbox" id="checkbox_{{ category|replace(' ', '_') }}" name="category_{{ category }}" {% if category in selected_categories %}checked="checked"{% endif %} />{{- '' -}}
<label for="checkbox_{{ category|replace(' ', '_') }}">{{ _(category) }}</label> <label for="checkbox_{{ category|replace(' ', '_') }}">{{ _(category) }}</label>
{%- endfor %} {%- endfor %}
{%- else -%} {%- else -%}
{% for category in categories -%} {% for category in categories_as_tabs -%}
<input class="hidden" type="checkbox" id="checkbox_{{ category|replace(' ', '_') }}" name="category_{{ category }}" {% if category in selected_categories %}checked="checked"{% endif %} />{{- '' -}} <input class="hidden" type="checkbox" id="checkbox_{{ category|replace(' ', '_') }}" name="category_{{ category }}" {% if category in selected_categories %}checked="checked"{% endif %} />{{- '' -}}
<label for="checkbox_{{ category|replace(' ', '_') }}">{{ _(category) }}</label> <label for="checkbox_{{ category|replace(' ', '_') }}">{{ _(category) }}</label>
{%- endfor %} {%- endfor %}

View file

@ -298,7 +298,7 @@
<div class="tab-pane active_if_nojs" id="tab_engine"> <div class="tab-pane active_if_nojs" id="tab_engine">
<!-- Nav tabs --> <!-- Nav tabs -->
<ul class="nav nav-tabs nav-justified hide_if_nojs" role="tablist"> <ul class="nav nav-tabs nav-justified hide_if_nojs" role="tablist">
{% for categ in all_categories %} {% for categ in categories_as_tabs + [OTHER_CATEGORY] %}
<li{% if loop.first %} class="active"{% endif %}><a href="#tab_engine_{{ categ|replace(' ', '_') }}" role="tab" data-toggle="tab">{{ _(categ) }}</a></li> <li{% if loop.first %} class="active"{% endif %}><a href="#tab_engine_{{ categ|replace(' ', '_') }}" role="tab" data-toggle="tab">{{ _(categ) }}</a></li>
{% endfor %} {% endfor %}
</ul> </ul>
@ -317,10 +317,13 @@
</p> </p>
</div> </div>
{% for categ in all_categories %} {% for categ in categories_as_tabs + [OTHER_CATEGORY] %}
<noscript><label>{{ _(categ) }}</label> <noscript><label>{{ _(categ) }}</label>
</noscript> </noscript>
<div class="tab-pane{% if loop.first %} active{% endif %} active_if_nojs" id="tab_engine_{{ categ|replace(' ', '_') }}"> <div class="tab-pane{% if loop.first %} active{% endif %} active_if_nojs" id="tab_engine_{{ categ|replace(' ', '_') }}">
{% if categ == OTHER_CATEGORY %}
<p>{{_('This tab does not show up for search results but you can search the engines listed here via bangs.')}}</p>
{% endif %}
<div class="container-fluid"> <div class="container-fluid">
<fieldset> <fieldset>
<div class="table-responsive"> <div class="table-responsive">
@ -348,7 +351,11 @@
<th scope="col" class="text-right">{{ _("Allow") }}</th> <th scope="col" class="text-right">{{ _("Allow") }}</th>
{% endif %} {% endif %}
</tr> </tr>
{% for search_engine in engines_by_category[categ] %} {% for group, engines in engines_by_category[categ] | group_engines_in_tab %}
{% if loop.length > 1 %}
<tr><th colspan="9">{{_(group)}}</th></tr>
{% endif %}
{% for search_engine in engines %}
{% if not search_engine.private %} {% if not search_engine.private %}
<tr> <tr>
{% if not rtl %} {% if not rtl %}
@ -357,7 +364,11 @@
</td> </td>
<th scope="row" data-engine-name="{{ search_engine.name }}"><span aria-labelledby="{{ 'tooltip_' + categ + '_' + search_engine.name }}"> <th scope="row" data-engine-name="{{ search_engine.name }}"><span aria-labelledby="{{ 'tooltip_' + categ + '_' + search_engine.name }}">
{%- if search_engine.enable_http %}{{ icon('exclamation-sign', 'No HTTPS') }}{% endif -%} {%- if search_engine.enable_http %}{{ icon('exclamation-sign', 'No HTTPS') }}{% endif -%}
{{- search_engine.name -}}</span> {{- search_engine.name -}}
{%- if search_engine.about and search_engine.about.language %}
({{search_engine.about.language | upper}})
{%- endif %}
</span>
{{- engine_about(search_engine, 'tooltip_' + categ + '_' + search_engine.name) -}} {{- engine_about(search_engine, 'tooltip_' + categ + '_' + search_engine.name) -}}
</th> </th>
<td class="name">{{ shortcuts[search_engine.name] }}</td> <td class="name">{{ shortcuts[search_engine.name] }}</td>
@ -382,6 +393,7 @@
{% endif %} {% endif %}
</tr> </tr>
{% endif %} {% endif %}
{% endfor %}
{% endfor %} {% endfor %}
</table> </table>
</div> </div>

View file

@ -14,7 +14,7 @@
<div id="categories" class="search_categories">{{- '' -}} <div id="categories" class="search_categories">{{- '' -}}
<div id="categories_container"> <div id="categories_container">
{%- if display_tooltip %}<div class="help">{{ _('Click on the magnifier to perform search') }}</div>{% endif -%} {%- if display_tooltip %}<div class="help">{{ _('Click on the magnifier to perform search') }}</div>{% endif -%}
{%- for category in categories -%} {%- for category in categories_as_tabs -%}
<div class="category"><input type="checkbox" id="checkbox_{{ category|replace(' ', '_') }}" name="category_{{ category }}"{% if category in selected_categories %} checked="checked"{% endif %}/> <div class="category"><input type="checkbox" id="checkbox_{{ category|replace(' ', '_') }}" name="category_{{ category }}"{% if category in selected_categories %} checked="checked"{% endif %}/>
<label for="checkbox_{{ category|replace(' ', '_') }}" class="tooltips"> <label for="checkbox_{{ category|replace(' ', '_') }}" class="tooltips">
{{- icon_big(category_icons[category]) if category in category_icons else icon_big('globe-outline') -}} {{- icon_big(category_icons[category]) if category in category_icons else icon_big('globe-outline') -}}

View file

@ -274,8 +274,11 @@
{{ tab_header('maintab', 'engines', _('Engines')) }} {{ tab_header('maintab', 'engines', _('Engines')) }}
<p>{{ _('Currently used search engines') }}</p> <p>{{ _('Currently used search engines') }}</p>
{{ tabs_open() }} {{ tabs_open() }}
{% for categ in all_categories %} {% for categ in categories_as_tabs + [OTHER_CATEGORY] %}
{{ tab_header('enginetab', 'category' + categ, _(categ)) }} {{ tab_header('enginetab', 'category' + categ, _(categ)) }}
{% if categ == OTHER_CATEGORY %}
<p>{{_('This tab does not show up for search results but you can search the engines listed here via bangs.')}}</p>
{% endif %}
<div class="scrollx"> <div class="scrollx">
<table class="striped"> <table class="striped">
<tr> <tr>
@ -289,12 +292,22 @@
<th>{{ _("Max time") }}</th> <th>{{ _("Max time") }}</th>
<th>{{ _("Reliability") }}</th> <th>{{ _("Reliability") }}</th>
</tr> </tr>
{% for search_engine in engines_by_category[categ] %} {% for group, engines in engines_by_category[categ] | group_engines_in_tab %}
{% if loop.length > 1 %}
<tr><th colspan="9" class="engine-group">{{_(group)}}</th></tr>
{% endif %}
{% for search_engine in engines %}
{% if not search_engine.private %} {% if not search_engine.private %}
{% set engine_id = 'engine_' + search_engine.name|replace(' ', '_') + '__' + categ|replace(' ', '_') %} {% set engine_id = 'engine_' + search_engine.name|replace(' ', '_') + '__' + categ|replace(' ', '_') %}
<tr> <tr>
<td class="engine_checkbox">{{ checkbox_onoff(engine_id, (search_engine.name, categ) in disabled_engines) }}</td> <td class="engine_checkbox">{{ checkbox_onoff(engine_id, (search_engine.name, categ) in disabled_engines) }}</td>
<th class="name" data-engine-name="{{ search_engine.name }}">{% if search_engine.enable_http %}{{ icon_big('warning', 'No HTTPS') }}{% endif %} {{ search_engine.name }} {{ engine_about(search_engine) }}</th> <th class="name" data-engine-name="{{ search_engine.name }}">{% if search_engine.enable_http %}{{ icon_big('warning', 'No HTTPS') }}{% endif %}
{{ search_engine.name }}
{%- if search_engine.about and search_engine.about.language %}
({{search_engine.about.language | upper}})
{%- endif %}
{{ engine_about(search_engine) }}
</th>
<td class="shortcut">{{ shortcuts[search_engine.name] }}</td> <td class="shortcut">{{ shortcuts[search_engine.name] }}</td>
<td>{{ checkbox(engine_id + '_supported_languages', supports[search_engine.name]['supports_selected_language'], true, true) }}</td> <td>{{ checkbox(engine_id + '_supported_languages', supports[search_engine.name]['supports_selected_language'], true, true) }}</td>
<td>{{ checkbox(engine_id + '_safesearch', supports[search_engine.name]['safesearch'], true, true) }}</td> <td>{{ checkbox(engine_id + '_safesearch', supports[search_engine.name]['safesearch'], true, true) }}</td>
@ -305,6 +318,7 @@
</tr> </tr>
{% endif %} {% endif %}
{% endfor %} {% endfor %}
{% endfor %}
</table> </table>
</div> </div>
{{ tab_footer() }} {{ tab_footer() }}

View file

@ -59,6 +59,7 @@ from searx.settings_defaults import OUTPUT_FORMATS
from searx.settings_loader import get_default_settings_path from searx.settings_loader import get_default_settings_path
from searx.exceptions import SearxParameterException from searx.exceptions import SearxParameterException
from searx.engines import ( from searx.engines import (
OTHER_CATEGORY,
categories, categories,
engines, engines,
engine_shortcuts, engine_shortcuts,
@ -73,6 +74,8 @@ from searx.webutils import (
new_hmac, new_hmac,
is_hmac_of, is_hmac_of,
is_flask_run_cmdline, is_flask_run_cmdline,
DEFAULT_GROUP_NAME,
group_engines_in_tab,
) )
from searx.webadapter import ( from searx.webadapter import (
get_search_query_from_webapp, get_search_query_from_webapp,
@ -152,6 +155,7 @@ app = Flask(__name__, static_folder=settings['ui']['static_path'], template_fold
app.jinja_env.trim_blocks = True app.jinja_env.trim_blocks = True
app.jinja_env.lstrip_blocks = True app.jinja_env.lstrip_blocks = True
app.jinja_env.add_extension('jinja2.ext.loopcontrols') # pylint: disable=no-member app.jinja_env.add_extension('jinja2.ext.loopcontrols') # pylint: disable=no-member
app.jinja_env.filters['group_engines_in_tab'] = group_engines_in_tab # pylint: disable=no-member
app.secret_key = settings['server']['secret_key'] app.secret_key = settings['server']['secret_key']
babel = Babel(app) babel = Babel(app)
@ -169,6 +173,17 @@ _category_names = (
gettext('map'), gettext('map'),
gettext('onions'), gettext('onions'),
gettext('science'), gettext('science'),
# non-tab categories
gettext('apps'),
gettext('dictionaries'),
gettext('lyrics'),
gettext('packages'),
gettext('q&a'),
gettext('repos'),
gettext('software wikis'),
gettext('web'),
gettext(DEFAULT_GROUP_NAME),
gettext(OTHER_CATEGORY),
) )
_simple_style = (gettext('auto'), gettext('light'), gettext('dark')) _simple_style = (gettext('auto'), gettext('light'), gettext('dark'))
@ -390,12 +405,6 @@ def get_translations():
} }
def _get_ordered_categories():
ordered_categories = list(settings['ui']['categories_order'])
ordered_categories.extend(x for x in sorted(categories.keys()) if x not in ordered_categories)
return ordered_categories
def _get_enable_categories(all_categories): def _get_enable_categories(all_categories):
disabled_engines = request.preferences.engines.get_disabled() disabled_engines = request.preferences.engines.get_disabled()
enabled_categories = set( enabled_categories = set(
@ -430,8 +439,9 @@ def render(template_name, override_theme=None, **kwargs):
kwargs['query_in_title'] = request.preferences.get_value('query_in_title') kwargs['query_in_title'] = request.preferences.get_value('query_in_title')
kwargs['safesearch'] = str(request.preferences.get_value('safesearch')) kwargs['safesearch'] = str(request.preferences.get_value('safesearch'))
kwargs['theme'] = get_current_theme_name(override=override_theme) kwargs['theme'] = get_current_theme_name(override=override_theme)
kwargs['all_categories'] = _get_ordered_categories() kwargs['categories_as_tabs'] = list(settings['categories_as_tabs'].keys())
kwargs['categories'] = _get_enable_categories(kwargs['all_categories']) kwargs['categories'] = _get_enable_categories(categories.keys())
kwargs['OTHER_CATEGORY'] = OTHER_CATEGORY
# i18n # i18n
kwargs['language_codes'] = [l for l in languages if l[0] in settings['search']['languages']] kwargs['language_codes'] = [l for l in languages if l[0] in settings['search']['languages']]

View file

@ -5,11 +5,14 @@ import hashlib
import hmac import hmac
import re import re
import inspect import inspect
import itertools
from typing import Iterable, List, Tuple
from io import StringIO from io import StringIO
from codecs import getincrementalencoder from codecs import getincrementalencoder
from searx import logger from searx import logger, settings
from searx.engines import Engine, OTHER_CATEGORY
VALID_LANGUAGE_CODE = re.compile(r'^[a-z]{2,3}(-[a-zA-Z]{2})?$') VALID_LANGUAGE_CODE = re.compile(r'^[a-z]{2,3}(-[a-zA-Z]{2})?$')
@ -134,3 +137,28 @@ def is_flask_run_cmdline():
if len(frames) < 2: if len(frames) < 2:
return False return False
return frames[-2].filename.endswith('flask/cli.py') return frames[-2].filename.endswith('flask/cli.py')
DEFAULT_GROUP_NAME = 'others'
def group_engines_in_tab(engines: Iterable[Engine]) -> List[Tuple[str, Iterable[Engine]]]:
"""Groups an Iterable of engines by their first non tab category"""
def get_group(eng):
non_tab_categories = [
c for c in eng.categories if c not in list(settings['categories_as_tabs'].keys()) + [OTHER_CATEGORY]
]
return non_tab_categories[0] if len(non_tab_categories) > 0 else DEFAULT_GROUP_NAME
groups = itertools.groupby(sorted(engines, key=get_group), get_group)
def group_sort_key(group):
return (group[0] == DEFAULT_GROUP_NAME, group[0].lower())
sorted_groups = sorted(((name, list(engines)) for name, engines in groups), key=group_sort_key)
def engine_sort_key(engine):
return (engine.about.get('language', ''), engine.name)
return [(groupname, sorted(engines, key=engine_sort_key)) for groupname, engines in sorted_groups]

View file

@ -33,6 +33,10 @@ outgoing:
request_timeout: 1.0 # seconds request_timeout: 1.0 # seconds
useragent_suffix: "" useragent_suffix: ""
categories_as_tabs:
general:
dummy:
engines: engines:
- name: general dummy - name: general dummy
engine: dummy engine: dummy