diff options
66 files changed, 3293 insertions, 2112 deletions
diff --git a/.dir-locals.el b/.dir-locals.el new file mode 100644 index 000000000..d7ec87921 --- /dev/null +++ b/.dir-locals.el @@ -0,0 +1,133 @@ +;;; .dir-locals.el +;; +;; If you get ``*** EPC Error ***`` (even after a jedi:install-server) in your +;; emacs session, mostly you have jedi-mode enabled but the python enviroment is +;; missed. The python environment has to be next to the +;; ``<repo>/.dir-locals.el`` in:: +;; +;; ./local/py3 +;; +;; In Emacs, some buffer locals are referencing the project environment: +;; +;; - prj-root --> <repo>/ +;; - python-environment-directory --> <repo>/local +;; - python-environment-default-root-name --> py3 +;; - python-shell-virtualenv-root --> <repo>/local/py3 +;; When this variable is set with the path of the virtualenv to use, +;; `process-environment' and `exec-path' get proper values in order to run +;; shells inside the specified virtualenv, example:: +;; (setq python-shell-virtualenv-root "/path/to/env/") +;; +;; To setup such an environment build target 'pyenv' or 'pyenvinstall':: +;; +;; $ make pyenvinstall +;; +;; Alternatively create the virtualenv, source it and install jedi + epc +;; (required by `emacs-jedi <https://tkf.github.io/emacs-jedi>`_):: +;; +;; $ virtualenv --python=python3 "--no-site-packages" ./local/py3 +;; ... +;; $ source ./local/py3/bin/activate +;; (py3)$ # now install into the activated 'py3' environment .. +;; (py3)$ pip install jedi epc +;; ... +;; +;; Here is what also I found useful to add to my .emacs:: +;; +;; (global-set-key [f6] 'flycheck-mode) +;; (add-hook 'python-mode-hook 'my:python-mode-hook) +;; +;; (defun my:python-mode-hook () +;; (add-to-list 'company-backends 'company-jedi) +;; (require 'jedi-core) +;; (jedi:setup) +;; (define-key python-mode-map (kbd "C-c C-d") 'jedi:show-doc) +;; (define-key python-mode-map (kbd "M-.") 'jedi:goto-definition) +;; (define-key python-mode-map (kbd "M-,") 'jedi:goto-definition-pop-marker) +;; ) +;; + +((nil + . ((fill-column . 80) + )) + (python-mode + . ((indent-tabs-mode . nil) + + ;; project root folder is where the `.dir-locals.el' is located + (eval . (setq-local + prj-root (locate-dominating-file default-directory ".dir-locals.el"))) + + (eval . (setq-local + python-environment-directory (expand-file-name "./local" prj-root))) + + ;; use 'py3' enviroment as default + (eval . (setq-local + python-environment-default-root-name "py3")) + + (eval . (setq-local + python-shell-virtualenv-root + (concat python-environment-directory + "/" + python-environment-default-root-name))) + + ;; python-shell-virtualenv-path is obsolete, use python-shell-virtualenv-root! + ;; (eval . (setq-local + ;; python-shell-virtualenv-path python-shell-virtualenv-root)) + + (eval . (setq-local + python-shell-interpreter + (expand-file-name "bin/python" python-shell-virtualenv-root))) + + (eval . (setq-local + python-environment-virtualenv + (list (expand-file-name "bin/virtualenv" python-shell-virtualenv-root) + ;;"--system-site-packages" + "--quiet"))) + + (eval . (setq-local + pylint-command + (expand-file-name "bin/pylint" python-shell-virtualenv-root))) + + ;; pylint will find the '.pylintrc' file next to the CWD + ;; https://pylint.readthedocs.io/en/latest/user_guide/run.html#command-line-options + (eval . (setq-local + flycheck-pylintrc ".pylintrc")) + + ;; flycheck & other python stuff should use the local py3 environment + (eval . (setq-local + flycheck-python-pylint-executable python-shell-interpreter)) + + ;; use 'M-x jedi:show-setup-info' and 'M-x epc:controller' to inspect jedi server + + ;; https://tkf.github.io/emacs-jedi/latest/#jedi:environment-root -- You + ;; can specify a full path instead of a name (relative path). In that case, + ;; python-environment-directory is ignored and Python virtual environment + ;; is created at the specified path. + (eval . (setq-local jedi:environment-root python-shell-virtualenv-root)) + + ;; https://tkf.github.io/emacs-jedi/latest/#jedi:server-command + (eval .(setq-local + jedi:server-command + (list python-shell-interpreter + jedi:server-script) + )) + + ;; jedi:environment-virtualenv --> see above 'python-environment-virtualenv' + ;; is set buffer local! No need to setup jedi:environment-virtualenv: + ;; + ;; Virtualenv command to use. A list of string. If it is nil, + ;; python-environment-virtualenv is used instead. You must set non-nil + ;; value to jedi:environment-root in order to make this setting work. + ;; + ;; https://tkf.github.io/emacs-jedi/latest/#jedi:environment-virtualenv + ;; + ;; (eval . (setq-local + ;; jedi:environment-virtualenv + ;; (list (expand-file-name "bin/virtualenv" python-shell-virtualenv-root) + ;; ;;"--python" + ;; ;;"/usr/bin/python3.4" + ;; ))) + + ;; jedi:server-args + + ))) diff --git a/.gitignore b/.gitignore index db20da83e..828856f4c 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,6 @@ setup.cfg node_modules/ .tx/ + +local/ +searx.egg-info/ diff --git a/.pylintrc b/.pylintrc new file mode 100644 index 000000000..3b4adb2ca --- /dev/null +++ b/.pylintrc @@ -0,0 +1,444 @@ +# -*- coding: utf-8; mode: conf -*- +# lint Python modules using external checkers. +# +# This is the main checker controlling the other ones and the reports +# generation. It is itself both a raw checker and an astng checker in order +# to: +# * handle message activation / deactivation at the module level +# * handle some basic but necessary stats'data (number of classes, methods...) +# +[MASTER] + +# A comma-separated list of package or module names from where C extensions may +# be loaded. Extensions are loading into the active Python interpreter and may +# run arbitrary code +extension-pkg-whitelist= + +# Add files or directories to the blacklist. They should be base names, not +# paths. +ignore=CVS, .git, .svn + +# Add files or directories matching the regex patterns to the blacklist. The +# regex matches against base names, not paths. +ignore-patterns= + +# Python code to execute, usually for sys.path manipulation such as +# pygtk.require(). +#init-hook= + +# Use multiple processes to speed up Pylint. +jobs=1 + +# List of plugins (as comma separated values of python modules names) to load, +# usually to register additional checkers. +load-plugins= + +# Pickle collected data for later comparisons. +persistent=yes + +# Specify a configuration file. +#rcfile= + +# Allow loading of arbitrary C extensions. Extensions are imported into the +# active Python interpreter and may run arbitrary code. +unsafe-load-any-extension=no + + +[MESSAGES CONTROL] + +# Only show warnings with the listed confidence levels. Leave empty to show +# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED +confidence= + +# Disable the message, report, category or checker with the given id(s). You +# can either give multiple identifiers separated by comma (,) or put this +# option multiple times (only on the command line, not in the configuration +# file where it should appear only once).You can also use "--disable=all" to +# disable everything first and then reenable specific checks. For example, if +# you want to run only the similarities checker, you can use "--disable=all +# --enable=similarities". If you want to run only the classes checker, but have +# no Warning level messages displayed, use"--disable=all --enable=classes +# --disable=W" +disable=bad-whitespace, duplicate-code + +# Enable the message, report, category or checker with the given id(s). You can +# either give multiple identifier separated by comma (,) or put this option +# multiple time (only on the command line, not in the configuration file where +# it should appear only once). See also the "--disable" option for examples. +enable= + + +[REPORTS] + +# Python expression which should return a note less than 10 (10 is the highest +# note). You have access to the variables errors warning, statement which +# respectively contain the number of errors / warnings messages and the total +# number of statements analyzed. This is used by the global evaluation report +# (RP0004). +evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) + +# Template used to display messages. This is a python new-style format string +# used to format the message information. See doc for all details + +# HINT: do not set this here, use argument --msg-template=... +#msg-template={path}:{line}: [{msg_id}({symbol}),{obj}] {msg} + +# Set the output format. Available formats are text, parseable, colorized, json +# and msvs (visual studio).You can also give a reporter class, eg +# mypackage.mymodule.MyReporterClass. + +# HINT: do not set this here, use argument --output-format=... +#output-format=text + +# Tells whether to display a full report or only the messages +reports=no + +# Activate the evaluation score. +score=yes + + +[REFACTORING] + +# Maximum number of nested blocks for function / method body +max-nested-blocks=5 + + +[BASIC] + +# List of builtins function names that should not be used, separated by a comma +bad-functions=map,filter,apply,input + +# Naming hint for argument names +argument-name-hint=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ + +# Regular expression matching correct argument names +argument-rgx=(([a-z][a-zA-Z0-9_]{2,30})|(_[a-z0-9_]*))$ + +# Naming hint for attribute names +attr-name-hint=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ + +# Regular expression matching correct attribute names +attr-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*)|([A-Z0-9_]*))$ + +# Bad variable names which should always be refused, separated by a comma +bad-names=foo,bar,baz,toto,tutu,tata + +# Naming hint for class attribute names +class-attribute-name-hint=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ + +# Regular expression matching correct class attribute names +class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ + +# Naming hint for class names +class-name-hint=[A-Z_][a-zA-Z0-9]+$ + +# Regular expression matching correct class names +class-rgx=[A-Z_][a-zA-Z0-9]+$ + +# Naming hint for constant names +const-name-hint=(([A-Z_][A-Z0-9_]*)|(__.*__))$ + +# Regular expression matching correct constant names +const-rgx=(([a-zA-Z_][a-zA-Z0-9_]*)|(__.*__))$ +#const-rgx=[f]?[A-Z_][a-zA-Z0-9_]{2,30}$ + +# Minimum line length for functions/classes that require docstrings, shorter +# ones are exempt. +docstring-min-length=-1 + +# Naming hint for function names +function-name-hint=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ + +# Regular expression matching correct function names +function-rgx=(([a-z][a-zA-Z0-9_]{2,30})|(_[a-z0-9_]*))$ + +# Good variable names which should always be accepted, separated by a comma +good-names=i,j,k,ex,Run,_,log,cfg,id + +# Include a hint for the correct naming format with invalid-name +include-naming-hint=no + +# Naming hint for inline iteration names +inlinevar-name-hint=[A-Za-z_][A-Za-z0-9_]*$ + +# Regular expression matching correct inline iteration names +inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ + +# Naming hint for method names +method-name-hint=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ + +# Regular expression matching correct method names +method-rgx=(([a-z][a-zA-Z0-9_]{2,30})|(_[a-z0-9_]*))$ + +# Naming hint for module names +module-name-hint=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ + +# Regular expression matching correct module names +#module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ +module-rgx=([a-z_][a-z0-9_]*)$ + +# Colon-delimited sets of names that determine each other's naming style when +# the name regexes allow several styles. +name-group= + +# Regular expression which should only match function or class names that do +# not require a docstring. +no-docstring-rgx=^_ + +# List of decorators that produce properties, such as abc.abstractproperty. Add +# to this list to register other decorators that produce valid properties. +property-classes=abc.abstractproperty + +# Naming hint for variable names +variable-name-hint=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ + +# Regular expression matching correct variable names +variable-rgx=(([a-z][a-zA-Z0-9_]{2,30})|(_[a-z0-9_]*)|([a-z]))$ + + +[FORMAT] + +# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. +expected-line-ending-format= + +# Regexp for a line that is allowed to be longer than the limit. +ignore-long-lines=^\s*(# )?<?https?://\S+>?$ + +# Number of spaces of indent required inside a hanging or continued line. +indent-after-paren=4 + +# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 +# tab). +indent-string=' ' + +# Maximum number of characters on a single line. +max-line-length=120 + +# Maximum number of lines in a module +max-module-lines=2000 + +# List of optional constructs for which whitespace checking is disabled. `dict- +# separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}. +# `trailing-comma` allows a space between comma and closing bracket: (a, ). +# `empty-line` allows space-only lines. +no-space-check=trailing-comma,dict-separator + +# Allow the body of a class to be on the same line as the declaration if body +# contains single statement.No config file found, using default configuration + +single-line-class-stmt=no + +# Allow the body of an if to be on the same line as the test if there is no +# else. +single-line-if-stmt=no + + +[LOGGING] + +# Logging modules to check that the string format arguments are in logging +# function parameter format +logging-modules=logging + + +[MISCELLANEOUS] + +# List of note tags to take in consideration, separated by a comma. +notes=FIXME,XXX,TODO + + +[SIMILARITIES] + +# Ignore comments when computing similarities. +ignore-comments=yes + +# Ignore docstrings when computing similarities. +ignore-docstrings=yes + +# Ignore imports when computing similarities. +ignore-imports=no + +# Minimum lines number of a similarity. +min-similarity-lines=4 + + +[SPELLING] + +# Spelling dictionary name. Available dictionaries: none. To make it working +# install python-enchant package. +spelling-dict= + +# List of comma separated words that should not be checked. +spelling-ignore-words= + +# A path to a file that contains private dictionary; one word per line. +spelling-private-dict-file= + +# Tells whether to store unknown words to indicated private dictionary in +# --spelling-private-dict-file option instead of raising a message. +spelling-store-unknown-words=no + + +[TYPECHECK] + +# List of decorators that produce context managers, such as +# contextlib.contextmanager. Add to this list to register other decorators that +# produce valid context managers. +contextmanager-decorators=contextlib.contextmanager + +# List of members which are set dynamically and missed by pylint inference +# system, and so shouldn't trigger E1101 when accessed. Python regular +# expressions are accepted. +generated-members= + +# Tells whether missing members accessed in mixin class should be ignored. A +# mixin class is detected if its name ends with "mixin" (case insensitive). +ignore-mixin-members=yes + +# This flag controls whether pylint should warn about no-member and similar +# checks whenever an opaque object is returned when inferring. The inference +# can return multiple potential results while evaluating a Python object, but +# some branches might not be evaluated, which results in partial inference. In +# that case, it might be useful to still emit no-member and other checks for +# the rest of the inferred objects. +ignore-on-opaque-inference=yes + +# List of class names for which member attributes should not be checked (useful +# for classes with dynamically set attributes). This supports the use of +# qualified names. +ignored-classes=optparse.Values,thread._local,_thread._local + +# List of module names for which member attributes should not be checked +# (useful for modules/projects where namespaces are manipulated during runtime +# and thus existing member attributes cannot be deduced by static analysis. It +# supports qualified module names, as well as Unix pattern matching. +ignored-modules= + +# Show a hint with possible names when a member name was not found. The aspect +# of finding the hint is based on edit distance. +missing-member-hint=yes + +# The minimum edit distance a name should have in order to be considered a +# similar match for a missing member name. +missing-member-hint-distance=1 + +# The total number of similar names that should be taken in consideration when +# showing a hint for a missing member. +missing-member-max-choices=1 + + +[VARIABLES] + +# List of additional names supposed to be defined in builtins. Remember that +# you should avoid to define new builtins when possible. +additional-builtins= + +# Tells whether unused global variables should be treated as a violation. +allow-global-unused-variables=yes + +# List of strings which can identify a callback function by name. A callback +# name must start or end with one of those strings. +callbacks=cb_,_cb + +# A regular expression matching the name of dummy variables (i.e. expectedly +# not used). +dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ + +# Argument names that match this expression will be ignored. Default to name +# with leading underscore +ignored-argument-names=_.*|^ignored_|^unused_ + +# Tells whether we should check for unused import in __init__ files. +init-import=no + +# List of qualified module names which can have objects that can redefine +# builtins. +redefining-builtins-modules=six.moves,future.builtins + + +[CLASSES] + +# List of method names used to declare (i.e. assign) instance attributes. +defining-attr-methods=__init__,__new__,setUp + +# List of member names, which should be excluded from the protected access +# warning. +exclude-protected=_asdict,_fields,_replace,_source,_make + +# List of valid names for the first argument in a class method. +valid-classmethod-first-arg=cls + +# List of valid names for the first argument in a metaclass class method. +valid-metaclass-classmethod-first-arg=mcs + + +[DESIGN] + +# Maximum number of arguments for function / method +max-args=8 + +# Maximum number of attributes for a class (see R0902). +max-attributes=20 + +# Maximum number of boolean expressions in a if statement +max-bool-expr=5 + +# Maximum number of branch for function / method body +max-branches=12 + +# Maximum number of locals for function / method body +max-locals=20 + +# Maximum number of parents for a class (see R0901). +max-parents=7 + +# Maximum number of public methods for a class (see R0904). +max-public-methods=20 + +# Maximum number of return / yield for function / method body +max-returns=6 + +# Maximum number of statements in function / method body +max-statements=50 + +# Minimum number of public methods for a class (see R0903). +min-public-methods=2 + + +[IMPORTS] + +# Allow wildcard imports from modules that define __all__. +allow-wildcard-with-all=no + +# Analyse import fallback blocks. This can be used to support both Python 2 and +# 3 compatible code, which means that the block might have code that exists +# only in one or another interpreter, leading to false positives when analysed. +analyse-fallback-blocks=no + +# Deprecated modules which should not be used, separated by a comma +deprecated-modules=optparse,tkinter.tix + +# Create a graph of external dependencies in the given file (report RP0402 must +# not be disabled) +ext-import-graph= + +# Create a graph of every (i.e. internal and external) dependencies in the +# given file (report RP0402 must not be disabled) +import-graph= + +# Create a graph of internal dependencies in the given file (report RP0402 must +# not be disabled) +int-import-graph= + +# Force import order to recognize a module as part of the standard +# compatibility libraries. +known-standard-library= + +# Force import order to recognize a module as part of a third party library. +known-third-party=enchant + + +[EXCEPTIONS] + +# Exceptions that will emit a warning when being caught. Defaults to +# "Exception" +overgeneral-exceptions=Exception diff --git a/AUTHORS.rst b/AUTHORS.rst index 674bfd758..2a2f19219 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -1,4 +1,4 @@ -Searx was created by Adam Tauber and is maintained by Adam Tauber, Alexandre Flament and Noémi Ványi. +Searx was created by Adam Tauber and is maintained by Adam Tauber, Alexandre Flament, Noémi Ványi, @pofilo and Markus Heiser. Major contributing authors: @@ -9,6 +9,8 @@ Major contributing authors: - @Cqoicebordel - Noémi Ványi - Marc Abonce Seguin @a01200356 +- @pofilo +- Markus Heiser @return42 People who have submitted patches/translates, reported bugs, consulted features or generally made searx better: diff --git a/Dockerfile b/Dockerfile index 1815ddc57..b0b5a609d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,10 +1,17 @@ FROM alpine:3.10 +ENTRYPOINT ["/sbin/tini","--","/usr/local/searx/dockerfiles/docker-entrypoint.sh"] +EXPOSE 8080 +VOLUME /etc/searx +VOLUME /var/log/uwsgi -ARG VERSION_GITCOMMIT=unknow -ARG SEARX_GIT_VERSION=unknow +ARG VERSION_GITCOMMIT=unknown +ARG SEARX_GIT_VERSION=unknown -ARG SEARX_GID=1000 -ARG SEARX_UID=1000 +ARG SEARX_GID=977 +ARG SEARX_UID=977 + +RUN addgroup -g ${SEARX_GID} searx && \ + adduser -u ${SEARX_UID} -D -h /usr/local/searx -s /bin/sh -G searx searx ARG TIMESTAMP_SETTINGS=0 ARG TIMESTAMP_UWSGI=0 @@ -16,19 +23,14 @@ ENV INSTANCE_NAME=searx \ BASE_URL= \ MORTY_KEY= \ MORTY_URL= -EXPOSE 8080 -VOLUME /etc/searx -VOLUME /var/log/uwsgi WORKDIR /usr/local/searx -RUN addgroup -g ${SEARX_GID} searx && \ - adduser -u ${SEARX_UID} -D -h /usr/local/searx -s /bin/sh -G searx searx COPY requirements.txt ./requirements.txt -RUN apk -U upgrade \ - && apk add -t build-dependencies \ +RUN apk upgrade --no-cache \ + && apk add --no-cache -t build-dependencies \ build-base \ py3-setuptools \ python3-dev \ @@ -38,7 +40,7 @@ RUN apk -U upgrade \ openssl-dev \ tar \ git \ - && apk add \ + && apk add --no-cache \ ca-certificates \ su-exec \ python3 \ @@ -50,8 +52,7 @@ RUN apk -U upgrade \ uwsgi-python3 \ && pip3 install --upgrade pip \ && pip3 install --no-cache -r requirements.txt \ - && apk del build-dependencies \ - && rm -f /var/cache/apk/* + && apk del build-dependencies COPY --chown=searx:searx . . @@ -62,7 +63,6 @@ RUN su searx -c "/usr/bin/python3 -m compileall -q searx"; \ echo "VERSION_STRING = VERSION_STRING + \"-$VERSION_GITCOMMIT\"" >> /usr/local/searx/searx/version.py; \ fi -ENTRYPOINT ["/sbin/tini","--","/usr/local/searx/dockerfiles/docker-entrypoint.sh"] # Keep this argument at the end since it change each time ARG LABEL_DATE= diff --git a/Makefile b/Makefile new file mode 100644 index 000000000..77ffe489f --- /dev/null +++ b/Makefile @@ -0,0 +1,62 @@ +# -*- coding: utf-8; mode: makefile-gmake -*- + +PYOBJECTS = searx +PY_SETUP_EXTRAS ?= \[test\] + +include utils/makefile.include +include utils/makefile.python + +all: clean install + +PHONY += help +help: + @echo ' test - run developer tests' + @echo ' run - run developer instance' + @echo ' install - developer install (./local)' + @echo ' uninstall - uninstall (./local)' + @echo '' + @$(MAKE) -s -f utils/makefile.include make-help + @echo '' + @$(MAKE) -s -f utils/makefile.python python-help + +PHONY += install +install: pyenvinstall + +PHONY += uninstall +uninstall: pyenvuninstall + +PHONY += clean +clean: pyclean + $(call cmd,common_clean) + +PHONY += run +run: pyenvinstall + $(Q) ( \ + sed -i -e "s/debug : False/debug : True/g" ./searx/settings.yml ; \ + sleep 2 ; \ + xdg-open http://127.0.0.1:8888/ ; \ + sleep 3 ; \ + sed -i -e "s/debug : True/debug : False/g" ./searx/settings.yml ; \ + ) & + $(PY_ENV)/bin/python ./searx/webapp.py + +# test +# ---- + +PHONY += test test.pylint test.pep8 test.unit test.robot + +# TODO: balance linting with pylint +test: test.pep8 test.unit test.robot + - make pylint + +test.pep8: pyenvinstall + $(PY_ENV_ACT); ./manage.sh pep8_check + +test.unit: pyenvinstall + $(PY_ENV_ACT); ./manage.sh unit_tests + +test.robot: pyenvinstall + $(PY_ENV_ACT); ./manage.sh install_geckodriver + $(PY_ENV_ACT); ./manage.sh robot_tests + +.PHONY: $(PHONY) diff --git a/searx/engines/bing.py b/searx/engines/bing.py index 1e614867b..ed0b87dbd 100644 --- a/searx/engines/bing.py +++ b/searx/engines/bing.py @@ -18,7 +18,7 @@ from lxml import html from searx import logger, utils from searx.engines.xpath import extract_text from searx.url_utils import urlencode -from searx.utils import match_language, gen_useragent +from searx.utils import match_language, gen_useragent, eval_xpath logger = logger.getChild('bing engine') @@ -65,11 +65,11 @@ def response(resp): dom = html.fromstring(resp.text) # parse results - for result in dom.xpath('//div[@class="sa_cc"]'): - link = result.xpath('.//h3/a')[0] + for result in eval_xpath(dom, '//div[@class="sa_cc"]'): + link = eval_xpath(result, './/h3/a')[0] url = link.attrib.get('href') title = extract_text(link) - content = extract_text(result.xpath('.//p')) + content = extract_text(eval_xpath(result, './/p')) # append result results.append({'url': url, @@ -77,11 +77,11 @@ def response(resp): 'content': content}) # parse results again if nothing is found yet - for result in dom.xpath('//li[@class="b_algo"]'): - link = result.xpath('.//h2/a')[0] + for result in eval_xpath(dom, '//li[@class="b_algo"]'): + link = eval_xpath(result, './/h2/a')[0] url = link.attrib.get('href') title = extract_text(link) - content = extract_text(result.xpath('.//p')) + content = extract_text(eval_xpath(result, './/p')) # append result results.append({'url': url, @@ -89,7 +89,7 @@ def response(resp): 'content': content}) try: - result_len_container = "".join(dom.xpath('//span[@class="sb_count"]/text()')) + result_len_container = "".join(eval_xpath(dom, '//span[@class="sb_count"]/text()')) result_len_container = utils.to_string(result_len_container) if "-" in result_len_container: # Remove the part "from-to" for paginated request ... @@ -113,9 +113,9 @@ def response(resp): def _fetch_supported_languages(resp): supported_languages = [] dom = html.fromstring(resp.text) - options = dom.xpath('//div[@id="limit-languages"]//input') + options = eval_xpath(dom, '//div[@id="limit-languages"]//input') for option in options: - code = option.xpath('./@id')[0].replace('_', '-') + code = eval_xpath(option, './@id')[0].replace('_', '-') if code == 'nb': code = 'no' supported_languages.append(code) diff --git a/searx/engines/dictzone.py b/searx/engines/dictzone.py index 09db048cc..423af0971 100644 --- a/searx/engines/dictzone.py +++ b/searx/engines/dictzone.py @@ -11,7 +11,7 @@ import re from lxml import html -from searx.utils import is_valid_lang +from searx.utils import is_valid_lang, eval_xpath from searx.url_utils import urljoin categories = ['general'] @@ -47,14 +47,14 @@ def response(resp): dom = html.fromstring(resp.text) - for k, result in enumerate(dom.xpath(results_xpath)[1:]): + for k, result in enumerate(eval_xpath(dom, results_xpath)[1:]): try: - from_result, to_results_raw = result.xpath('./td') + from_result, to_results_raw = eval_xpath(result, './td') except: continue to_results = [] - for to_result in to_results_raw.xpath('./p/a'): + for to_result in eval_xpath(to_results_raw, './p/a'): t = to_result.text_content() if t.strip(): to_results.append(to_result.text_content()) diff --git a/searx/engines/doku.py b/searx/engines/doku.py index a391be444..d20e66026 100644 --- a/searx/engines/doku.py +++ b/searx/engines/doku.py @@ -11,6 +11,7 @@ from lxml.html import fromstring from searx.engines.xpath import extract_text +from searx.utils import eval_xpath from searx.url_utils import urlencode # engine dependent config @@ -45,16 +46,16 @@ def response(resp): # parse results # Quickhits - for r in doc.xpath('//div[@class="search_quickresult"]/ul/li'): + for r in eval_xpath(doc, '//div[@class="search_quickresult"]/ul/li'): try: - res_url = r.xpath('.//a[@class="wikilink1"]/@href')[-1] + res_url = eval_xpath(r, './/a[@class="wikilink1"]/@href')[-1] except: continue if not res_url: continue - title = extract_text(r.xpath('.//a[@class="wikilink1"]/@title')) + title = extract_text(eval_xpath(r, './/a[@class="wikilink1"]/@title')) # append result results.append({'title': title, @@ -62,13 +63,13 @@ def response(resp): 'url': base_url + res_url}) # Search results - for r in doc.xpath('//dl[@class="search_results"]/*'): + for r in eval_xpath(doc, '//dl[@class="search_results"]/*'): try: if r.tag == "dt": - res_url = r.xpath('.//a[@class="wikilink1"]/@href')[-1] - title = extract_text(r.xpath('.//a[@class="wikilink1"]/@title')) + res_url = eval_xpath(r, './/a[@class="wikilink1"]/@href')[-1] + title = extract_text(eval_xpath(r, './/a[@class="wikilink1"]/@title')) elif r.tag == "dd": - content = extract_text(r.xpath('.')) + content = extract_text(eval_xpath(r, '.')) # append result results.append({'title': title, diff --git a/searx/engines/duckduckgo.py b/searx/engines/duckduckgo.py index e77ef0126..0d2c0af2d 100644 --- a/searx/engines/duckduckgo.py +++ b/searx/engines/duckduckgo.py @@ -18,7 +18,7 @@ from json import loads from searx.engines.xpath import extract_text from searx.poolrequests import get from searx.url_utils import urlencode -from searx.utils import match_language +from searx.utils import match_language, eval_xpath # engine dependent config categories = ['general'] @@ -106,19 +106,19 @@ def response(resp): doc = fromstring(resp.text) # parse results - for i, r in enumerate(doc.xpath(result_xpath)): + for i, r in enumerate(eval_xpath(doc, result_xpath)): if i >= 30: break try: - res_url = r.xpath(url_xpath)[-1] + res_url = eval_xpath(r, url_xpath)[-1] except: continue if not res_url: continue - title = extract_text(r.xpath(title_xpath)) - content = extract_text(r.xpath(content_xpath)) + title = extract_text(eval_xpath(r, title_xpath)) + content = extract_text(eval_xpath(r, content_xpath)) # append result results.append({'title': title, diff --git a/searx/engines/duckduckgo_definitions.py b/searx/engines/duckduckgo_definitions.py index 957a13ea6..79d10c303 100644 --- a/searx/engines/duckduckgo_definitions.py +++ b/searx/engines/duckduckgo_definitions.py @@ -1,3 +1,14 @@ +""" +DuckDuckGo (definitions) + +- `Instant Answer API`_ +- `DuckDuckGo query`_ + +.. _Instant Answer API: https://duckduckgo.com/api +.. _DuckDuckGo query: https://api.duckduckgo.com/?q=DuckDuckGo&format=json&pretty=1 + +""" + import json from lxml import html from re import compile @@ -25,7 +36,8 @@ def result_to_text(url, text, htmlResult): def request(query, params): params['url'] = url.format(query=urlencode({'q': query})) language = match_language(params['language'], supported_languages, language_aliases) - params['headers']['Accept-Language'] = language.split('-')[0] + language = language.split('-')[0] + params['headers']['Accept-Language'] = language return params @@ -43,8 +55,9 @@ def response(resp): # add answer if there is one answer = search_res.get('Answer', '') - if answer != '': - results.append({'answer': html_to_text(answer)}) + if answer: + if search_res.get('AnswerType', '') not in ['calc']: + results.append({'answer': html_to_text(answer)}) # add infobox if 'Definition' in search_res: diff --git a/searx/engines/duden.py b/searx/engines/duden.py index 444f18c1f..cf2f1a278 100644 --- a/searx/engines/duden.py +++ b/searx/engines/duden.py @@ -11,6 +11,7 @@ from lxml import html, etree import re from searx.engines.xpath import extract_text +from searx.utils import eval_xpath from searx.url_utils import quote, urljoin from searx import logger @@ -52,9 +53,9 @@ def response(resp): dom = html.fromstring(resp.text) try: - number_of_results_string = re.sub('[^0-9]', '', dom.xpath( - '//a[@class="active" and contains(@href,"/suchen/dudenonline")]/span/text()')[0] - ) + number_of_results_string =\ + re.sub('[^0-9]', '', + eval_xpath(dom, '//a[@class="active" and contains(@href,"/suchen/dudenonline")]/span/text()')[0]) results.append({'number_of_results': int(number_of_results_string)}) @@ -62,12 +63,12 @@ def response(resp): logger.debug("Couldn't read number of results.") pass - for result in dom.xpath('//section[not(contains(@class, "essay"))]'): + for result in eval_xpath(dom, '//section[not(contains(@class, "essay"))]'): try: - url = result.xpath('.//h2/a')[0].get('href') + url = eval_xpath(result, './/h2/a')[0].get('href') url = urljoin(base_url, url) - title = result.xpath('string(.//h2/a)').strip() - content = extract_text(result.xpath('.//p')) + title = eval_xpath(result, 'string(.//h2/a)').strip() + content = extract_text(eval_xpath(result, './/p')) # append result results.append({'url': url, 'title': title, diff --git a/searx/engines/framalibre.py b/searx/engines/framalibre.py index 146cdaeec..f3441fa5f 100644 --- a/searx/engines/framalibre.py +++ b/searx/engines/framalibre.py @@ -10,7 +10,10 @@ @parse url, title, content, thumbnail, img_src """ -from cgi import escape +try: + from cgi import escape +except: + from html import escape from lxml import html from searx.engines.xpath import extract_text from searx.url_utils import urljoin, urlencode diff --git a/searx/engines/gigablast.py b/searx/engines/gigablast.py index 6b0402233..a84f3f69d 100644 --- a/searx/engines/gigablast.py +++ b/searx/engines/gigablast.py @@ -15,6 +15,7 @@ from json import loads from time import time from lxml.html import fromstring from searx.url_utils import urlencode +from searx.utils import eval_xpath # engine dependent config categories = ['general'] @@ -99,9 +100,9 @@ def response(resp): def _fetch_supported_languages(resp): supported_languages = [] dom = fromstring(resp.text) - links = dom.xpath('//span[@id="menu2"]/a') + links = eval_xpath(dom, '//span[@id="menu2"]/a') for link in links: - href = link.xpath('./@href')[0].split('lang%3A') + href = eval_xpath(link, './@href')[0].split('lang%3A') if len(href) == 2: code = href[1].split('_') if len(code) == 2: diff --git a/searx/engines/google.py b/searx/engines/google.py index 03f0523e7..eed3a044e 100644 --- a/searx/engines/google.py +++ b/searx/engines/google.py @@ -14,7 +14,7 @@ from lxml import html, etree from searx.engines.xpath import extract_text, extract_url from searx import logger from searx.url_utils import urlencode, urlparse, parse_qsl -from searx.utils import match_language +from searx.utils import match_language, eval_xpath logger = logger.getChild('google engine') @@ -107,13 +107,12 @@ images_path = '/images' supported_languages_url = 'https://www.google.com/preferences?#languages' # specific xpath variables -results_xpath = '//div[@class="g"]' -url_xpath = './/h3/a/@href' -title_xpath = './/h3' -content_xpath = './/span[@class="st"]' -content_misc_xpath = './/div[@class="f slp"]' -suggestion_xpath = '//p[@class="_Bmc"]' -spelling_suggestion_xpath = '//a[@class="spell"]' +results_xpath = '//div[contains(@class, "ZINbbc")]' +url_xpath = './/div[@class="kCrYT"][1]/a/@href' +title_xpath = './/div[@class="kCrYT"][1]/a/div[1]' +content_xpath = './/div[@class="kCrYT"][2]//div[contains(@class, "BNeawe")]//div[contains(@class, "BNeawe")]' +suggestion_xpath = '//div[contains(@class, "ZINbbc")][last()]//div[@class="rVLSBd"]/a//div[contains(@class, "BNeawe")]' +spelling_suggestion_xpath = '//div[@id="scc"]//a' # map : detail location map_address_xpath = './/div[@class="s"]//table//td[2]/span/text()' @@ -156,7 +155,7 @@ def parse_url(url_string, google_hostname): # returns extract_text on the first result selected by the xpath or None def extract_text_from_dom(result, xpath): - r = result.xpath(xpath) + r = eval_xpath(result, xpath) if len(r) > 0: return extract_text(r[0]) return None @@ -199,9 +198,6 @@ def request(query, params): params['headers']['Accept-Language'] = language + ',' + language + '-' + country params['headers']['Accept'] = 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8' - # Force Internet Explorer 12 user agent to avoid loading the new UI that Searx can't parse - params['headers']['User-Agent'] = "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)" - params['google_hostname'] = google_hostname return params @@ -226,21 +222,21 @@ def response(resp): # convert the text to dom dom = html.fromstring(resp.text) - instant_answer = dom.xpath('//div[@id="_vBb"]//text()') + instant_answer = eval_xpath(dom, '//div[@id="_vBb"]//text()') if instant_answer: results.append({'answer': u' '.join(instant_answer)}) try: - results_num = int(dom.xpath('//div[@id="resultStats"]//text()')[0] + results_num = int(eval_xpath(dom, '//div[@id="resultStats"]//text()')[0] .split()[1].replace(',', '')) results.append({'number_of_results': results_num}) except: pass # parse results - for result in dom.xpath(results_xpath): + for result in eval_xpath(dom, results_xpath): try: - title = extract_text(result.xpath(title_xpath)[0]) - url = parse_url(extract_url(result.xpath(url_xpath), google_url), google_hostname) + title = extract_text(eval_xpath(result, title_xpath)[0]) + url = parse_url(extract_url(eval_xpath(result, url_xpath), google_url), google_hostname) parsed_url = urlparse(url, google_hostname) # map result @@ -249,7 +245,7 @@ def response(resp): continue # if parsed_url.path.startswith(maps_path) or parsed_url.netloc.startswith(map_hostname_start): # print "yooooo"*30 - # x = result.xpath(map_near) + # x = eval_xpath(result, map_near) # if len(x) > 0: # # map : near the location # results = results + parse_map_near(parsed_url, x, google_hostname) @@ -273,9 +269,7 @@ def response(resp): content = extract_text_from_dom(result, content_xpath) if content is None: continue - content_misc = extract_text_from_dom(result, content_misc_xpath) - if content_misc is not None: - content = content_misc + "<br />" + content + # append result results.append({'url': url, 'title': title, @@ -286,11 +280,11 @@ def response(resp): continue # parse suggestion - for suggestion in dom.xpath(suggestion_xpath): + for suggestion in eval_xpath(dom, suggestion_xpath): # append suggestion results.append({'suggestion': extract_text(suggestion)}) - for correction in dom.xpath(spelling_suggestion_xpath): + for correction in eval_xpath(dom, spelling_suggestion_xpath): results.append({'correction': extract_text(correction)}) # return results @@ -299,9 +293,9 @@ def response(resp): def parse_images(result, google_hostname): results = [] - for image in result.xpath(images_xpath): - url = parse_url(extract_text(image.xpath(image_url_xpath)[0]), google_hostname) - img_src = extract_text(image.xpath(image_img_src_xpath)[0]) + for image in eval_xpath(result, images_xpath): + url = parse_url(extract_text(eval_xpath(image, image_url_xpath)[0]), google_hostname) + img_src = extract_text(eval_xpath(image, image_img_src_xpath)[0]) # append result results.append({'url': url, @@ -388,10 +382,10 @@ def attributes_to_html(attributes): def _fetch_supported_languages(resp): supported_languages = {} dom = html.fromstring(resp.text) - options = dom.xpath('//*[@id="langSec"]//input[@name="lr"]') + options = eval_xpath(dom, '//*[@id="langSec"]//input[@name="lr"]') for option in options: - code = option.xpath('./@value')[0].split('_')[-1] - name = option.xpath('./@data-name')[0].title() + code = eval_xpath(option, './@value')[0].split('_')[-1] + name = eval_xpath(option, './@data-name')[0].title() supported_languages[code] = {"name": name} return supported_languages diff --git a/searx/engines/google_images.py b/searx/engines/google_images.py index d9a49e9cc..636913114 100644 --- a/searx/engines/google_images.py +++ b/searx/engines/google_images.py @@ -70,11 +70,21 @@ def response(resp): try: metadata = loads(result) - img_format = "{0} {1}x{2}".format(metadata['ity'], str(metadata['ow']), str(metadata['oh'])) - source = "{0} ({1})".format(metadata['st'], metadata['isu']) + + img_format = metadata.get('ity', '') + img_width = metadata.get('ow', '') + img_height = metadata.get('oh', '') + if img_width and img_height: + img_format += " {0}x{1}".format(img_width, img_height) + + source = metadata.get('st', '') + source_url = metadata.get('isu', '') + if source_url: + source += " ({0})".format(source_url) + results.append({'url': metadata['ru'], 'title': metadata['pt'], - 'content': metadata['s'], + 'content': metadata.get('s', ''), 'source': source, 'img_format': img_format, 'thumbnail_src': metadata['tu'], diff --git a/searx/engines/seedpeer.py b/searx/engines/seedpeer.py new file mode 100644 index 000000000..f9b1f99c8 --- /dev/null +++ b/searx/engines/seedpeer.py @@ -0,0 +1,78 @@ +# Seedpeer (Videos, Music, Files) +# +# @website https://seedpeer.me +# @provide-api no (nothing found) +# +# @using-api no +# @results HTML (using search portal) +# @stable yes (HTML can change) +# @parse url, title, content, seed, leech, magnetlink + +from lxml import html +from json import loads +from operator import itemgetter +from searx.url_utils import quote, urljoin +from searx.engines.xpath import extract_text + + +url = 'https://seedpeer.me/' +search_url = url + 'search/{search_term}?page={page_no}' +torrent_file_url = url + 'torrent/{torrent_hash}' + +# specific xpath variables +script_xpath = '//script[@type="text/javascript"][not(@src)]' +torrent_xpath = '(//table)[2]/tbody/tr' +link_xpath = '(./td)[1]/a/@href' +age_xpath = '(./td)[2]' +size_xpath = '(./td)[3]' + + +# do search-request +def request(query, params): + params['url'] = search_url.format(search_term=quote(query), + page_no=params['pageno']) + return params + + +# get response from search-request +def response(resp): + results = [] + dom = html.fromstring(resp.text) + result_rows = dom.xpath(torrent_xpath) + + try: + script_element = dom.xpath(script_xpath)[0] + json_string = script_element.text[script_element.text.find('{'):] + torrents_json = loads(json_string) + except: + return [] + + # parse results + for torrent_row, torrent_json in zip(result_rows, torrents_json['data']['list']): + title = torrent_json['name'] + seed = int(torrent_json['seeds']) + leech = int(torrent_json['peers']) + size = int(torrent_json['size']) + torrent_hash = torrent_json['hash'] + + torrentfile = torrent_file_url.format(torrent_hash=torrent_hash) + magnetlink = 'magnet:?xt=urn:btih:{}'.format(torrent_hash) + + age = extract_text(torrent_row.xpath(age_xpath)) + link = torrent_row.xpath(link_xpath)[0] + + href = urljoin(url, link) + + # append result + results.append({'url': href, + 'title': title, + 'content': age, + 'seed': seed, + 'leech': leech, + 'filesize': size, + 'torrentfile': torrentfile, + 'magnetlink': magnetlink, + 'template': 'torrent.html'}) + + # return results sorted by seeder + return sorted(results, key=itemgetter('seed'), reverse=True) diff --git a/searx/engines/soundcloud.py b/searx/engines/soundcloud.py index 870998545..284689bf6 100644 --- a/searx/engines/soundcloud.py +++ b/searx/engines/soundcloud.py @@ -51,7 +51,9 @@ def get_client_id(): if response.ok: tree = html.fromstring(response.content) - script_tags = tree.xpath("//script[contains(@src, '/assets/app')]") + # script_tags has been moved from /assets/app/ to /assets/ path. I + # found client_id in https://a-v2.sndcdn.com/assets/49-a0c01933-3.js + script_tags = tree.xpath("//script[contains(@src, '/assets/')]") app_js_urls = [script_tag.get('src') for script_tag in script_tags if script_tag is not None] # extracts valid app_js urls from soundcloud.com content diff --git a/searx/engines/startpage.py b/searx/engines/startpage.py index 0f0ec6e18..76567396f 100644 --- a/searx/engines/startpage.py +++ b/searx/engines/startpage.py @@ -16,6 +16,7 @@ from datetime import datetime, timedelta import re from searx.engines.xpath import extract_text from searx.languages import language_codes +from searx.utils import eval_xpath # engine dependent config categories = ['general'] @@ -70,8 +71,8 @@ def response(resp): dom = html.fromstring(resp.text) # parse results - for result in dom.xpath(results_xpath): - links = result.xpath(link_xpath) + for result in eval_xpath(dom, results_xpath): + links = eval_xpath(result, link_xpath) if not links: continue link = links[0] @@ -87,8 +88,8 @@ def response(resp): title = extract_text(link) - if result.xpath(content_xpath): - content = extract_text(result.xpath(content_xpath)) + if eval_xpath(result, content_xpath): + content = extract_text(eval_xpath(result, content_xpath)) else: content = '' diff --git a/searx/engines/wikidata.py b/searx/engines/wikidata.py index 5ea2b9958..e913b3915 100644 --- a/searx/engines/wikidata.py +++ b/searx/engines/wikidata.py @@ -16,7 +16,7 @@ from searx.poolrequests import get from searx.engines.xpath import extract_text from searx.engines.wikipedia import _fetch_supported_languages, supported_languages_url from searx.url_utils import urlencode -from searx.utils import match_language +from searx.utils import match_language, eval_xpath from json import loads from lxml.html import fromstring @@ -57,22 +57,6 @@ language_fallback_xpath = '//sup[contains(@class,"wb-language-fallback-indicator calendar_name_xpath = './/sup[contains(@class,"wb-calendar-name")]' media_xpath = value_xpath + '//div[contains(@class,"commons-media-caption")]//a' -# xpath_cache -xpath_cache = {} - - -def get_xpath(xpath_str): - result = xpath_cache.get(xpath_str, None) - if not result: - result = etree.XPath(xpath_str) - xpath_cache[xpath_str] = result - return result - - -def eval_xpath(element, xpath_str): - xpath = get_xpath(xpath_str) - return xpath(element) - def get_id_cache(result): id_cache = {} diff --git a/searx/engines/xpath.py b/searx/engines/xpath.py index 61494ce4e..b75896cc7 100644 --- a/searx/engines/xpath.py +++ b/searx/engines/xpath.py @@ -1,6 +1,6 @@ from lxml import html from lxml.etree import _ElementStringResult, _ElementUnicodeResult -from searx.utils import html_to_text +from searx.utils import html_to_text, eval_xpath from searx.url_utils import unquote, urlencode, urljoin, urlparse search_url = None @@ -104,15 +104,15 @@ def response(resp): results = [] dom = html.fromstring(resp.text) if results_xpath: - for result in dom.xpath(results_xpath): - url = extract_url(result.xpath(url_xpath), search_url) - title = extract_text(result.xpath(title_xpath)) - content = extract_text(result.xpath(content_xpath)) + for result in eval_xpath(dom, results_xpath): + url = extract_url(eval_xpath(result, url_xpath), search_url) + title = extract_text(eval_xpath(result, title_xpath)) + content = extract_text(eval_xpath(result, content_xpath)) tmp_result = {'url': url, 'title': title, 'content': content} # add thumbnail if available if thumbnail_xpath: - thumbnail_xpath_result = result.xpath(thumbnail_xpath) + thumbnail_xpath_result = eval_xpath(result, thumbnail_xpath) if len(thumbnail_xpath_result) > 0: tmp_result['img_src'] = extract_url(thumbnail_xpath_result, search_url) @@ -120,14 +120,14 @@ def response(resp): else: for url, title, content in zip( (extract_url(x, search_url) for - x in dom.xpath(url_xpath)), - map(extract_text, dom.xpath(title_xpath)), - map(extract_text, dom.xpath(content_xpath)) + x in eval_xpath(dom, url_xpath)), + map(extract_text, eval_xpath(dom, title_xpath)), + map(extract_text, eval_xpath(dom, content_xpath)) ): results.append({'url': url, 'title': title, 'content': content}) if not suggestion_xpath: return results - for suggestion in dom.xpath(suggestion_xpath): + for suggestion in eval_xpath(dom, suggestion_xpath): results.append({'suggestion': extract_text(suggestion)}) return results diff --git a/searx/engines/yahoo.py b/searx/engines/yahoo.py index 73b78bcf7..36c1a11f8 100644 --- a/searx/engines/yahoo.py +++ b/searx/engines/yahoo.py @@ -14,7 +14,7 @@ from lxml import html from searx.engines.xpath import extract_text, extract_url from searx.url_utils import unquote, urlencode -from searx.utils import match_language +from searx.utils import match_language, eval_xpath # engine dependent config categories = ['general'] @@ -109,21 +109,21 @@ def response(resp): dom = html.fromstring(resp.text) try: - results_num = int(dom.xpath('//div[@class="compPagination"]/span[last()]/text()')[0] + results_num = int(eval_xpath(dom, '//div[@class="compPagination"]/span[last()]/text()')[0] .split()[0].replace(',', '')) results.append({'number_of_results': results_num}) except: pass # parse results - for result in dom.xpath(results_xpath): + for result in eval_xpath(dom, results_xpath): try: - url = parse_url(extract_url(result.xpath(url_xpath), search_url)) - title = extract_text(result.xpath(title_xpath)[0]) + url = parse_url(extract_url(eval_xpath(result, url_xpath), search_url)) + title = extract_text(eval_xpath(result, title_xpath)[0]) except: continue - content = extract_text(result.xpath(content_xpath)[0]) + content = extract_text(eval_xpath(result, content_xpath)[0]) # append result results.append({'url': url, @@ -131,7 +131,7 @@ def response(resp): 'content': content}) # if no suggestion found, return results - suggestions = dom.xpath(suggestion_xpath) + suggestions = eval_xpath(dom, suggestion_xpath) if not suggestions: return results @@ -148,9 +148,9 @@ def response(resp): def _fetch_supported_languages(resp): supported_languages = [] dom = html.fromstring(resp.text) - options = dom.xpath('//div[@id="yschlang"]/span/label/input') + options = eval_xpath(dom, '//div[@id="yschlang"]/span/label/input') for option in options: - code_parts = option.xpath('./@value')[0][5:].split('_') + code_parts = eval_xpath(option, './@value')[0][5:].split('_') if len(code_parts) == 2: code = code_parts[0] + '-' + code_parts[1].upper() else: diff --git a/searx/results.py b/searx/results.py index a127024c8..3b1e4bd62 100644 --- a/searx/results.py +++ b/searx/results.py @@ -67,8 +67,9 @@ def merge_two_infoboxes(infobox1, infobox2): for url2 in infobox2.get('urls', []): unique_url = True - for url1 in infobox1.get('urls', []): - if compare_urls(urlparse(url1.get('url', '')), urlparse(url2.get('url', ''))): + parsed_url2 = urlparse(url2.get('url', '')) + for url1 in urls1: + if compare_urls(urlparse(url1.get('url', '')), parsed_url2): unique_url = False break if unique_url: @@ -188,8 +189,9 @@ class ResultContainer(object): add_infobox = True infobox_id = infobox.get('id', None) if infobox_id is not None: + parsed_url_infobox_id = urlparse(infobox_id) for existingIndex in self.infoboxes: - if compare_urls(urlparse(existingIndex.get('id', '')), urlparse(infobox_id)): + if compare_urls(urlparse(existingIndex.get('id', '')), parsed_url_infobox_id): merge_two_infoboxes(existingIndex, infobox) add_infobox = False diff --git a/searx/settings.yml b/searx/settings.yml index 835fbe5f6..c6f805331 100644 --- a/searx/settings.yml +++ b/searx/settings.yml @@ -744,10 +744,15 @@ engines: title_xpath : ./h2 content_xpath : ./p[@class="s"] suggestion_xpath : /html/body//div[@class="top-info"]/p[@class="top-info spell"]/a - first_page_num : 1 + first_page_num : 0 page_size : 10 disabled : True + - name : seedpeer + shortcut : speu + engine : seedpeer + categories: files, music, videos + # - name : yacy # engine : yacy # shortcut : ya diff --git a/searx/static/themes/legacy/less/autocompleter.less b/searx/static/themes/legacy/less/autocompleter.less index db9601aeb..4ab2508f8 100644 --- a/searx/static/themes/legacy/less/autocompleter.less +++ b/searx/static/themes/legacy/less/autocompleter.less @@ -1,61 +1,61 @@ -/*
- * searx, A privacy-respecting, hackable metasearch engine
- */
-
-ul {
- &.autocompleter-choices {
- position: absolute;
- margin: 0;
- padding: 0;
- list-style: none;
- border: 1px solid @color-autocompleter-choices-border;
- border-left-color: @color-autocompleter-choices-border-left-right;
- border-right-color: @color-autocompleter-choices-border-left-right;
- border-bottom-color: @color-autocompleter-choices-border-bottom;
- text-align: left;
- font-family: Verdana, Geneva, Arial, Helvetica, sans-serif;
- z-index: 50;
- background-color: @color-autocompleter-choices-background;
- color: @color-autocompleter-choices-font;
-
- li {
- position: relative;
- margin: -2px 0 0 0;
- padding: 0.2em 1.5em 0.2em 1em;
- display: block;
- float: none !important;
- cursor: pointer;
- font-weight: normal;
- white-space: nowrap;
- font-size: 1em;
- line-height: 1.5em;
-
- &.autocompleter-selected {
- background-color: @color-autocompleter-selected-background;
- color: @color-autocompleter-selected-font;
-
- span.autocompleter-queried {
- color: @color-autocompleter-selected-queried-font;
- }
- }
- }
-
- span.autocompleter-queried {
- display: inline;
- float: none;
- font-weight: bold;
- margin: 0;
- padding: 0;
- }
- }
-}
-
-/*.autocompleter-loading {
- //background-image: url(images/spinner.gif);
- background-repeat: no-repeat;
- background-position: right 50%;
-}*/
-
-/*textarea.autocompleter-loading {
- background-position: right bottom;
-}*/
+/* + * searx, A privacy-respecting, hackable metasearch engine + */ + +ul { + &.autocompleter-choices { + position: absolute; + margin: 0; + padding: 0; + list-style: none; + border: 1px solid @color-autocompleter-choices-border; + border-left-color: @color-autocompleter-choices-border-left-right; + border-right-color: @color-autocompleter-choices-border-left-right; + border-bottom-color: @color-autocompleter-choices-border-bottom; + text-align: left; + font-family: Verdana, Geneva, Arial, Helvetica, sans-serif; + z-index: 50; + background-color: @color-autocompleter-choices-background; + color: @color-autocompleter-choices-font; + + li { + position: relative; + margin: -2px 0 0 0; + padding: 0.2em 1.5em 0.2em 1em; + display: block; + float: none !important; + cursor: pointer; + font-weight: normal; + white-space: nowrap; + font-size: 1em; + line-height: 1.5em; + + &.autocompleter-selected { + background-color: @color-autocompleter-selected-background; + color: @color-autocompleter-selected-font; + + span.autocompleter-queried { + color: @color-autocompleter-selected-queried-font; + } + } + } + + span.autocompleter-queried { + display: inline; + float: none; + font-weight: bold; + margin: 0; + padding: 0; + } + } +} + +/*.autocompleter-loading { + //background-image: url(images/spinner.gif); + background-repeat: no-repeat; + background-position: right 50%; +}*/ + +/*textarea.autocompleter-loading { + background-position: right bottom; +}*/ diff --git a/searx/static/themes/oscar/gruntfile.js b/searx/static/themes/oscar/gruntfile.js index 591399449..def035dba 100644 --- a/searx/static/themes/oscar/gruntfile.js +++ b/searx/static/themes/oscar/gruntfile.js @@ -24,7 +24,7 @@ module.exports = function(grunt) { jshint: { files: ['gruntfile.js', 'js/searx_src/*.js'], options: { - reporterOutput: "", + reporterOutput: "", // options here to override JSHint defaults globals: { jQuery: true, @@ -55,7 +55,7 @@ module.exports = function(grunt) { "css/logicodev-dark.min.css": "less/logicodev-dark/oscar.less"} }, /* - // built with ./manage.sh styles + // built with ./manage.sh styles bootstrap: { options: { paths: ["less/bootstrap"], @@ -90,7 +90,7 @@ module.exports = function(grunt) { grunt.registerTask('test', ['jshint']); grunt.registerTask('default', ['jshint', 'concat', 'uglify', 'less']); - + grunt.registerTask('styles', ['less']); }; diff --git a/searx/static/themes/oscar/js/searx.js b/searx/static/themes/oscar/js/searx.js index 58b38f019..927aeb422 100644 --- a/searx/static/themes/oscar/js/searx.js +++ b/searx/static/themes/oscar/js/searx.js @@ -1,26 +1,26 @@ -/**
- * searx is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * searx is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with searx. If not, see < http://www.gnu.org/licenses/ >.
- *
- * (C) 2014 by Thomas Pointhuber, <thomas.pointhuber@gmx.at>
- */
-
-requirejs.config({
- baseUrl: './static/themes/oscar/js',
- paths: {
- app: '../app'
- }
-});
+/** + * searx is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * searx is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with searx. If not, see < http://www.gnu.org/licenses/ >. + * + * (C) 2014 by Thomas Pointhuber, <thomas.pointhuber@gmx.at> + */ + +requirejs.config({ + baseUrl: './static/themes/oscar/js', + paths: { + app: '../app' + } +}); ;/** * searx is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by @@ -51,306 +51,306 @@ window.searx = (function(d) { method: script.getAttribute('data-method') }; })(document); -;/**
- * searx is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * searx is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with searx. If not, see < http://www.gnu.org/licenses/ >.
- *
- * (C) 2014 by Thomas Pointhuber, <thomas.pointhuber@gmx.at>
- */
-
-if(searx.autocompleter) {
- searx.searchResults = new Bloodhound({
- datumTokenizer: Bloodhound.tokenizers.obj.whitespace('value'),
- queryTokenizer: Bloodhound.tokenizers.whitespace,
- remote: './autocompleter?q=%QUERY'
- });
- searx.searchResults.initialize();
-}
-
-$(document).ready(function(){
- if(searx.autocompleter) {
- $('#q').typeahead(null, {
- name: 'search-results',
- displayKey: function(result) {
- return result;
- },
- source: searx.searchResults.ttAdapter()
- });
- }
-});
-;/**
- * searx is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * searx is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with searx. If not, see < http://www.gnu.org/licenses/ >.
- *
- * (C) 2014 by Thomas Pointhuber, <thomas.pointhuber@gmx.at>
- */
-
-$(document).ready(function(){
- /**
- * focus element if class="autofocus" and id="q"
- */
- $('#q.autofocus').focus();
-
- /**
- * select full content on click if class="select-all-on-click"
- */
- $(".select-all-on-click").click(function () {
- $(this).select();
- });
-
- /**
- * change text during btn-collapse click if possible
- */
- $('.btn-collapse').click(function() {
- var btnTextCollapsed = $(this).data('btn-text-collapsed');
- var btnTextNotCollapsed = $(this).data('btn-text-not-collapsed');
-
- if(btnTextCollapsed !== '' && btnTextNotCollapsed !== '') {
- if($(this).hasClass('collapsed')) {
- new_html = $(this).html().replace(btnTextCollapsed, btnTextNotCollapsed);
- } else {
- new_html = $(this).html().replace(btnTextNotCollapsed, btnTextCollapsed);
- }
- $(this).html(new_html);
- }
- });
-
- /**
- * change text during btn-toggle click if possible
- */
- $('.btn-toggle .btn').click(function() {
- var btnClass = 'btn-' + $(this).data('btn-class');
- var btnLabelDefault = $(this).data('btn-label-default');
- var btnLabelToggled = $(this).data('btn-label-toggled');
- if(btnLabelToggled !== '') {
- if($(this).hasClass('btn-default')) {
- new_html = $(this).html().replace(btnLabelDefault, btnLabelToggled);
- } else {
- new_html = $(this).html().replace(btnLabelToggled, btnLabelDefault);
- }
- $(this).html(new_html);
- }
- $(this).toggleClass(btnClass);
- $(this).toggleClass('btn-default');
- });
-
- /**
- * change text during btn-toggle click if possible
- */
- $('.media-loader').click(function() {
- var target = $(this).data('target');
- var iframe_load = $(target + ' > iframe');
- var srctest = iframe_load.attr('src');
- if(srctest === undefined || srctest === false){
- iframe_load.attr('src', iframe_load.data('src'));
- }
- });
-
- /**
- * Select or deselect every categories on double clic
- */
- $(".btn-sm").dblclick(function() {
- var btnClass = 'btn-' + $(this).data('btn-class'); // primary
- if($(this).hasClass('btn-default')) {
- $(".btn-sm > input").attr('checked', 'checked');
- $(".btn-sm > input").prop("checked", true);
- $(".btn-sm").addClass(btnClass);
- $(".btn-sm").addClass('active');
- $(".btn-sm").removeClass('btn-default');
- } else {
- $(".btn-sm > input").attr('checked', '');
- $(".btn-sm > input").removeAttr('checked');
- $(".btn-sm > input").checked = false;
- $(".btn-sm").removeClass(btnClass);
- $(".btn-sm").removeClass('active');
- $(".btn-sm").addClass('btn-default');
- }
- });
-});
-;/**
- * searx is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * searx is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with searx. If not, see < http://www.gnu.org/licenses/ >.
- *
- * (C) 2014 by Thomas Pointhuber, <thomas.pointhuber@gmx.at>
- */
-
-$(document).ready(function(){
- $(".searx_overpass_request").on( "click", function( event ) {
- var overpass_url = "https://overpass-api.de/api/interpreter?data=";
- var query_start = overpass_url + "[out:json][timeout:25];(";
- var query_end = ");out meta;";
-
- var osm_id = $(this).data('osm-id');
- var osm_type = $(this).data('osm-type');
- var result_table = $(this).data('result-table');
- var result_table_loadicon = "#" + $(this).data('result-table-loadicon');
-
- // tags which can be ignored
- var osm_ignore_tags = [ "addr:city", "addr:country", "addr:housenumber", "addr:postcode", "addr:street" ];
-
- if(osm_id && osm_type && result_table) {
- result_table = "#" + result_table;
- var query = null;
- switch(osm_type) {
- case 'node':
- query = query_start + "node(" + osm_id + ");" + query_end;
- break;
- case 'way':
- query = query_start + "way(" + osm_id + ");" + query_end;
- break;
- case 'relation':
- query = query_start + "relation(" + osm_id + ");" + query_end;
- break;
- default:
- break;
- }
- if(query) {
- //alert(query);
- var ajaxRequest = $.ajax( query )
- .done(function( html) {
- if(html && html.elements && html.elements[0]) {
- var element = html.elements[0];
- var newHtml = $(result_table).html();
- for (var row in element.tags) {
- if(element.tags.name === null || osm_ignore_tags.indexOf(row) == -1) {
- newHtml += "<tr><td>" + row + "</td><td>";
- switch(row) {
- case "phone":
- case "fax":
- newHtml += "<a href=\"tel:" + element.tags[row].replace(/ /g,'') + "\">" + element.tags[row] + "</a>";
- break;
- case "email":
- newHtml += "<a href=\"mailto:" + element.tags[row] + "\">" + element.tags[row] + "</a>";
- break;
- case "website":
- case "url":
- newHtml += "<a href=\"" + element.tags[row] + "\">" + element.tags[row] + "</a>";
- break;
- case "wikidata":
- newHtml += "<a href=\"https://www.wikidata.org/wiki/" + element.tags[row] + "\">" + element.tags[row] + "</a>";
- break;
- case "wikipedia":
- if(element.tags[row].indexOf(":") != -1) {
- newHtml += "<a href=\"https://" + element.tags[row].substring(0,element.tags[row].indexOf(":")) + ".wikipedia.org/wiki/" + element.tags[row].substring(element.tags[row].indexOf(":")+1) + "\">" + element.tags[row] + "</a>";
- break;
- }
- /* jshint ignore:start */
- default:
- /* jshint ignore:end */
- newHtml += element.tags[row];
- break;
- }
- newHtml += "</td></tr>";
- }
- }
- $(result_table).html(newHtml);
- $(result_table).removeClass('hidden');
- $(result_table_loadicon).addClass('hidden');
- }
- })
- .fail(function() {
- $(result_table_loadicon).html($(result_table_loadicon).html() + "<p class=\"text-muted\">could not load data!</p>");
- });
- }
- }
-
- // this event occour only once per element
- $( this ).off( event );
- });
-
- $(".searx_init_map").on( "click", function( event ) {
- var leaflet_target = $(this).data('leaflet-target');
- var map_lon = $(this).data('map-lon');
- var map_lat = $(this).data('map-lat');
- var map_zoom = $(this).data('map-zoom');
- var map_boundingbox = $(this).data('map-boundingbox');
- var map_geojson = $(this).data('map-geojson');
-
- require(['leaflet-0.7.3.min'], function(leaflet) {
- if(map_boundingbox) {
- southWest = L.latLng(map_boundingbox[0], map_boundingbox[2]);
- northEast = L.latLng(map_boundingbox[1], map_boundingbox[3]);
- map_bounds = L.latLngBounds(southWest, northEast);
- }
-
- // TODO hack
- // change default imagePath
- L.Icon.Default.imagePath = "./static/themes/oscar/img/map";
-
- // init map
- var map = L.map(leaflet_target);
-
- // create the tile layer with correct attribution
- var osmMapnikUrl='https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png';
- var osmMapnikAttrib='Map data © <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 beta | Maps data © <a href="https://openstreetmap.org">OpenStreetMap</a> contributors';
- var osmWikimedia = new L.TileLayer(osmWikimediaUrl, {minZoom: 1, maxZoom: 19, attribution: osmWikimediaAttrib});
-
- // init map view
- if(map_bounds) {
- // TODO hack: https://github.com/Leaflet/Leaflet/issues/2021
- setTimeout(function () {
- map.fitBounds(map_bounds, {
- maxZoom:17
- });
- }, 0);
- } else if (map_lon && map_lat) {
- if(map_zoom)
- map.setView(new L.LatLng(map_lat, map_lon),map_zoom);
- else
- map.setView(new L.LatLng(map_lat, map_lon),8);
- }
-
- map.addLayer(osmMapnik);
-
- var baseLayers = {
- "OSM Mapnik": osmMapnik/*,
- "OSM Wikimedia": osmWikimedia*/
- };
-
- L.control.layers(baseLayers).addTo(map);
-
-
- if(map_geojson)
- L.geoJson(map_geojson).addTo(map);
- /*else if(map_bounds)
- L.rectangle(map_bounds, {color: "#ff7800", weight: 3, fill:false}).addTo(map);*/
- });
-
- // this event occour only once per element
- $( this ).off( event );
- });
-});
+;/** + * searx is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * searx is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with searx. If not, see < http://www.gnu.org/licenses/ >. + * + * (C) 2014 by Thomas Pointhuber, <thomas.pointhuber@gmx.at> + */ + +if(searx.autocompleter) { + searx.searchResults = new Bloodhound({ + datumTokenizer: Bloodhound.tokenizers.obj.whitespace('value'), + queryTokenizer: Bloodhound.tokenizers.whitespace, + remote: './autocompleter?q=%QUERY' + }); + searx.searchResults.initialize(); +} + +$(document).ready(function(){ + if(searx.autocompleter) { + $('#q').typeahead(null, { + name: 'search-results', + displayKey: function(result) { + return result; + }, + source: searx.searchResults.ttAdapter() + }); + } +}); +;/** + * searx is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * searx is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with searx. If not, see < http://www.gnu.org/licenses/ >. + * + * (C) 2014 by Thomas Pointhuber, <thomas.pointhuber@gmx.at> + */ + +$(document).ready(function(){ + /** + * focus element if class="autofocus" and id="q" + */ + $('#q.autofocus').focus(); + + /** + * select full content on click if class="select-all-on-click" + */ + $(".select-all-on-click").click(function () { + $(this).select(); + }); + + /** + * change text during btn-collapse click if possible + */ + $('.btn-collapse').click(function() { + var btnTextCollapsed = $(this).data('btn-text-collapsed'); + var btnTextNotCollapsed = $(this).data('btn-text-not-collapsed'); + + if(btnTextCollapsed !== '' && btnTextNotCollapsed !== '') { + if($(this).hasClass('collapsed')) { + new_html = $(this).html().replace(btnTextCollapsed, btnTextNotCollapsed); + } else { + new_html = $(this).html().replace(btnTextNotCollapsed, btnTextCollapsed); + } + $(this).html(new_html); + } + }); + + /** + * change text during btn-toggle click if possible + */ + $('.btn-toggle .btn').click(function() { + var btnClass = 'btn-' + $(this).data('btn-class'); + var btnLabelDefault = $(this).data('btn-label-default'); + var btnLabelToggled = $(this).data('btn-label-toggled'); + if(btnLabelToggled !== '') { + if($(this).hasClass('btn-default')) { + new_html = $(this).html().replace(btnLabelDefault, btnLabelToggled); + } else { + new_html = $(this).html().replace(btnLabelToggled, btnLabelDefault); + } + $(this).html(new_html); + } + $(this).toggleClass(btnClass); + $(this).toggleClass('btn-default'); + }); + + /** + * change text during btn-toggle click if possible + */ + $('.media-loader').click(function() { + var target = $(this).data('target'); + var iframe_load = $(target + ' > iframe'); + var srctest = iframe_load.attr('src'); + if(srctest === undefined || srctest === false){ + iframe_load.attr('src', iframe_load.data('src')); + } + }); + + /** + * Select or deselect every categories on double clic + */ + $(".btn-sm").dblclick(function() { + var btnClass = 'btn-' + $(this).data('btn-class'); // primary + if($(this).hasClass('btn-default')) { + $(".btn-sm > input").attr('checked', 'checked'); + $(".btn-sm > input").prop("checked", true); + $(".btn-sm").addClass(btnClass); + $(".btn-sm").addClass('active'); + $(".btn-sm").removeClass('btn-default'); + } else { + $(".btn-sm > input").attr('checked', ''); + $(".btn-sm > input").removeAttr('checked'); + $(".btn-sm > input").checked = false; + $(".btn-sm").removeClass(btnClass); + $(".btn-sm").removeClass('active'); + $(".btn-sm").addClass('btn-default'); + } + }); +}); +;/** + * searx is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * searx is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with searx. If not, see < http://www.gnu.org/licenses/ >. + * + * (C) 2014 by Thomas Pointhuber, <thomas.pointhuber@gmx.at> + */ + +$(document).ready(function(){ + $(".searx_overpass_request").on( "click", function( event ) { + var overpass_url = "https://overpass-api.de/api/interpreter?data="; + var query_start = overpass_url + "[out:json][timeout:25];("; + var query_end = ");out meta;"; + + var osm_id = $(this).data('osm-id'); + var osm_type = $(this).data('osm-type'); + var result_table = $(this).data('result-table'); + var result_table_loadicon = "#" + $(this).data('result-table-loadicon'); + + // tags which can be ignored + var osm_ignore_tags = [ "addr:city", "addr:country", "addr:housenumber", "addr:postcode", "addr:street" ]; + + if(osm_id && osm_type && result_table) { + result_table = "#" + result_table; + var query = null; + switch(osm_type) { + case 'node': + query = query_start + "node(" + osm_id + ");" + query_end; + break; + case 'way': + query = query_start + "way(" + osm_id + ");" + query_end; + break; + case 'relation': + query = query_start + "relation(" + osm_id + ");" + query_end; + break; + default: + break; + } + if(query) { + //alert(query); + var ajaxRequest = $.ajax( query ) + .done(function( html) { + if(html && html.elements && html.elements[0]) { + var element = html.elements[0]; + var newHtml = $(result_table).html(); + for (var row in element.tags) { + if(element.tags.name === null || osm_ignore_tags.indexOf(row) == -1) { + newHtml += "<tr><td>" + row + "</td><td>"; + switch(row) { + case "phone": + case "fax": + newHtml += "<a href=\"tel:" + element.tags[row].replace(/ /g,'') + "\">" + element.tags[row] + "</a>"; + break; + case "email": + newHtml += "<a href=\"mailto:" + element.tags[row] + "\">" + element.tags[row] + "</a>"; + break; + case "website": + case "url": + newHtml += "<a href=\"" + element.tags[row] + "\">" + element.tags[row] + "</a>"; + break; + case "wikidata": + newHtml += "<a href=\"https://www.wikidata.org/wiki/" + element.tags[row] + "\">" + element.tags[row] + "</a>"; + break; + case "wikipedia": + if(element.tags[row].indexOf(":") != -1) { + newHtml += "<a href=\"https://" + element.tags[row].substring(0,element.tags[row].indexOf(":")) + ".wikipedia.org/wiki/" + element.tags[row].substring(element.tags[row].indexOf(":")+1) + "\">" + element.tags[row] + "</a>"; + break; + } + /* jshint ignore:start */ + default: + /* jshint ignore:end */ + newHtml += element.tags[row]; + break; + } + newHtml += "</td></tr>"; + } + } + $(result_table).html(newHtml); + $(result_table).removeClass('hidden'); + $(result_table_loadicon).addClass('hidden'); + } + }) + .fail(function() { + $(result_table_loadicon).html($(result_table_loadicon).html() + "<p class=\"text-muted\">could not load data!</p>"); + }); + } + } + + // this event occour only once per element + $( this ).off( event ); + }); + + $(".searx_init_map").on( "click", function( event ) { + var leaflet_target = $(this).data('leaflet-target'); + var map_lon = $(this).data('map-lon'); + var map_lat = $(this).data('map-lat'); + var map_zoom = $(this).data('map-zoom'); + var map_boundingbox = $(this).data('map-boundingbox'); + var map_geojson = $(this).data('map-geojson'); + + require(['leaflet-0.7.3.min'], function(leaflet) { + if(map_boundingbox) { + southWest = L.latLng(map_boundingbox[0], map_boundingbox[2]); + northEast = L.latLng(map_boundingbox[1], map_boundingbox[3]); + map_bounds = L.latLngBounds(southWest, northEast); + } + + // TODO hack + // change default imagePath + L.Icon.Default.imagePath = "./static/themes/oscar/img/map"; + + // init map + var map = L.map(leaflet_target); + + // create the tile layer with correct attribution + var osmMapnikUrl='https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'; + var osmMapnikAttrib='Map data © <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 beta | Maps data © <a href="https://openstreetmap.org">OpenStreetMap</a> contributors'; + var osmWikimedia = new L.TileLayer(osmWikimediaUrl, {minZoom: 1, maxZoom: 19, attribution: osmWikimediaAttrib}); + + // init map view + if(map_bounds) { + // TODO hack: https://github.com/Leaflet/Leaflet/issues/2021 + setTimeout(function () { + map.fitBounds(map_bounds, { + maxZoom:17 + }); + }, 0); + } else if (map_lon && map_lat) { + if(map_zoom) + map.setView(new L.LatLng(map_lat, map_lon),map_zoom); + else + map.setView(new L.LatLng(map_lat, map_lon),8); + } + + map.addLayer(osmMapnik); + + var baseLayers = { + "OSM Mapnik": osmMapnik/*, + "OSM Wikimedia": osmWikimedia*/ + }; + + L.control.layers(baseLayers).addTo(map); + + + if(map_geojson) + L.geoJson(map_geojson).addTo(map); + /*else if(map_bounds) + L.rectangle(map_bounds, {color: "#ff7800", weight: 3, fill:false}).addTo(map);*/ + }); + + // this event occour only once per element + $( this ).off( event ); + }); +}); diff --git a/searx/static/themes/oscar/js/searx_src/00_requirejs_config.js b/searx/static/themes/oscar/js/searx_src/00_requirejs_config.js index 1aa434902..e7c2abdac 100644 --- a/searx/static/themes/oscar/js/searx_src/00_requirejs_config.js +++ b/searx/static/themes/oscar/js/searx_src/00_requirejs_config.js @@ -1,23 +1,23 @@ -/**
- * searx is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * searx is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with searx. If not, see < http://www.gnu.org/licenses/ >.
- *
- * (C) 2014 by Thomas Pointhuber, <thomas.pointhuber@gmx.at>
- */
-
-requirejs.config({
- baseUrl: './static/themes/oscar/js',
- paths: {
- app: '../app'
- }
-});
+/** + * searx is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * searx is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with searx. If not, see < http://www.gnu.org/licenses/ >. + * + * (C) 2014 by Thomas Pointhuber, <thomas.pointhuber@gmx.at> + */ + +requirejs.config({ + baseUrl: './static/themes/oscar/js', + paths: { + app: '../app' + } +}); diff --git a/searx/static/themes/oscar/js/searx_src/autocompleter.js b/searx/static/themes/oscar/js/searx_src/autocompleter.js index 70c66d2fc..0907f8e34 100644 --- a/searx/static/themes/oscar/js/searx_src/autocompleter.js +++ b/searx/static/themes/oscar/js/searx_src/autocompleter.js @@ -1,37 +1,37 @@ -/**
- * searx is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * searx is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with searx. If not, see < http://www.gnu.org/licenses/ >.
- *
- * (C) 2014 by Thomas Pointhuber, <thomas.pointhuber@gmx.at>
- */
-
-if(searx.autocompleter) {
- searx.searchResults = new Bloodhound({
- datumTokenizer: Bloodhound.tokenizers.obj.whitespace('value'),
- queryTokenizer: Bloodhound.tokenizers.whitespace,
- remote: './autocompleter?q=%QUERY'
- });
- searx.searchResults.initialize();
-}
-
-$(document).ready(function(){
- if(searx.autocompleter) {
- $('#q').typeahead(null, {
- name: 'search-results',
- displayKey: function(result) {
- return result;
- },
- source: searx.searchResults.ttAdapter()
- });
- }
-});
+/** + * searx is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * searx is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with searx. If not, see < http://www.gnu.org/licenses/ >. + * + * (C) 2014 by Thomas Pointhuber, <thomas.pointhuber@gmx.at> + */ + +if(searx.autocompleter) { + searx.searchResults = new Bloodhound({ + datumTokenizer: Bloodhound.tokenizers.obj.whitespace('value'), + queryTokenizer: Bloodhound.tokenizers.whitespace, + remote: './autocompleter?q=%QUERY' + }); + searx.searchResults.initialize(); +} + +$(document).ready(function(){ + if(searx.autocompleter) { + $('#q').typeahead(null, { + name: 'search-results', + displayKey: function(result) { + return result; + }, + source: searx.searchResults.ttAdapter() + }); + } +}); diff --git a/searx/static/themes/oscar/js/searx_src/element_modifiers.js b/searx/static/themes/oscar/js/searx_src/element_modifiers.js index 8e4280548..4264d4c0d 100644 --- a/searx/static/themes/oscar/js/searx_src/element_modifiers.js +++ b/searx/static/themes/oscar/js/searx_src/element_modifiers.js @@ -1,99 +1,99 @@ -/**
- * searx is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * searx is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with searx. If not, see < http://www.gnu.org/licenses/ >.
- *
- * (C) 2014 by Thomas Pointhuber, <thomas.pointhuber@gmx.at>
- */
-
-$(document).ready(function(){
- /**
- * focus element if class="autofocus" and id="q"
- */
- $('#q.autofocus').focus();
-
- /**
- * select full content on click if class="select-all-on-click"
- */
- $(".select-all-on-click").click(function () {
- $(this).select();
- });
-
- /**
- * change text during btn-collapse click if possible
- */
- $('.btn-collapse').click(function() {
- var btnTextCollapsed = $(this).data('btn-text-collapsed');
- var btnTextNotCollapsed = $(this).data('btn-text-not-collapsed');
-
- if(btnTextCollapsed !== '' && btnTextNotCollapsed !== '') {
- if($(this).hasClass('collapsed')) {
- new_html = $(this).html().replace(btnTextCollapsed, btnTextNotCollapsed);
- } else {
- new_html = $(this).html().replace(btnTextNotCollapsed, btnTextCollapsed);
- }
- $(this).html(new_html);
- }
- });
-
- /**
- * change text during btn-toggle click if possible
- */
- $('.btn-toggle .btn').click(function() {
- var btnClass = 'btn-' + $(this).data('btn-class');
- var btnLabelDefault = $(this).data('btn-label-default');
- var btnLabelToggled = $(this).data('btn-label-toggled');
- if(btnLabelToggled !== '') {
- if($(this).hasClass('btn-default')) {
- new_html = $(this).html().replace(btnLabelDefault, btnLabelToggled);
- } else {
- new_html = $(this).html().replace(btnLabelToggled, btnLabelDefault);
- }
- $(this).html(new_html);
- }
- $(this).toggleClass(btnClass);
- $(this).toggleClass('btn-default');
- });
-
- /**
- * change text during btn-toggle click if possible
- */
- $('.media-loader').click(function() {
- var target = $(this).data('target');
- var iframe_load = $(target + ' > iframe');
- var srctest = iframe_load.attr('src');
- if(srctest === undefined || srctest === false){
- iframe_load.attr('src', iframe_load.data('src'));
- }
- });
-
- /**
- * Select or deselect every categories on double clic
- */
- $(".btn-sm").dblclick(function() {
- var btnClass = 'btn-' + $(this).data('btn-class'); // primary
- if($(this).hasClass('btn-default')) {
- $(".btn-sm > input").attr('checked', 'checked');
- $(".btn-sm > input").prop("checked", true);
- $(".btn-sm").addClass(btnClass);
- $(".btn-sm").addClass('active');
- $(".btn-sm").removeClass('btn-default');
- } else {
- $(".btn-sm > input").attr('checked', '');
- $(".btn-sm > input").removeAttr('checked');
- $(".btn-sm > input").checked = false;
- $(".btn-sm").removeClass(btnClass);
- $(".btn-sm").removeClass('active');
- $(".btn-sm").addClass('btn-default');
- }
- });
-});
+/** + * searx is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * searx is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with searx. If not, see < http://www.gnu.org/licenses/ >. + * + * (C) 2014 by Thomas Pointhuber, <thomas.pointhuber@gmx.at> + */ + +$(document).ready(function(){ + /** + * focus element if class="autofocus" and id="q" + */ + $('#q.autofocus').focus(); + + /** + * select full content on click if class="select-all-on-click" + */ + $(".select-all-on-click").click(function () { + $(this).select(); + }); + + /** + * change text during btn-collapse click if possible + */ + $('.btn-collapse').click(function() { + var btnTextCollapsed = $(this).data('btn-text-collapsed'); + var btnTextNotCollapsed = $(this).data('btn-text-not-collapsed'); + + if(btnTextCollapsed !== '' && btnTextNotCollapsed !== '') { + if($(this).hasClass('collapsed')) { + new_html = $(this).html().replace(btnTextCollapsed, btnTextNotCollapsed); + } else { + new_html = $(this).html().replace(btnTextNotCollapsed, btnTextCollapsed); + } + $(this).html(new_html); + } + }); + + /** + * change text during btn-toggle click if possible + */ + $('.btn-toggle .btn').click(function() { + var btnClass = 'btn-' + $(this).data('btn-class'); + var btnLabelDefault = $(this).data('btn-label-default'); + var btnLabelToggled = $(this).data('btn-label-toggled'); + if(btnLabelToggled !== '') { + if($(this).hasClass('btn-default')) { + new_html = $(this).html().replace(btnLabelDefault, btnLabelToggled); + } else { + new_html = $(this).html().replace(btnLabelToggled, btnLabelDefault); + } + $(this).html(new_html); + } + $(this).toggleClass(btnClass); + $(this).toggleClass('btn-default'); + }); + + /** + * change text during btn-toggle click if possible + */ + $('.media-loader').click(function() { + var target = $(this).data('target'); + var iframe_load = $(target + ' > iframe'); + var srctest = iframe_load.attr('src'); + if(srctest === undefined || srctest === false){ + iframe_load.attr('src', iframe_load.data('src')); + } + }); + + /** + * Select or deselect every categories on double clic + */ + $(".btn-sm").dblclick(function() { + var btnClass = 'btn-' + $(this).data('btn-class'); // primary + if($(this).hasClass('btn-default')) { + $(".btn-sm > input").attr('checked', 'checked'); + $(".btn-sm > input").prop("checked", true); + $(".btn-sm").addClass(btnClass); + $(".btn-sm").addClass('active'); + $(".btn-sm").removeClass('btn-default'); + } else { + $(".btn-sm > input").attr('checked', ''); + $(".btn-sm > input").removeAttr('checked'); + $(".btn-sm > input").checked = false; + $(".btn-sm").removeClass(btnClass); + $(".btn-sm").removeClass('active'); + $(".btn-sm").addClass('btn-default'); + } + }); +}); diff --git a/searx/static/themes/oscar/js/searx_src/leaflet_map.js b/searx/static/themes/oscar/js/searx_src/leaflet_map.js index 4be46acb5..3c8c616b1 100644 --- a/searx/static/themes/oscar/js/searx_src/leaflet_map.js +++ b/searx/static/themes/oscar/js/searx_src/leaflet_map.js @@ -1,167 +1,167 @@ -/**
- * searx is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * searx is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with searx. If not, see < http://www.gnu.org/licenses/ >.
- *
- * (C) 2014 by Thomas Pointhuber, <thomas.pointhuber@gmx.at>
- */
-
-$(document).ready(function(){
- $(".searx_overpass_request").on( "click", function( event ) {
- var overpass_url = "https://overpass-api.de/api/interpreter?data=";
- var query_start = overpass_url + "[out:json][timeout:25];(";
- var query_end = ");out meta;";
-
- var osm_id = $(this).data('osm-id');
- var osm_type = $(this).data('osm-type');
- var result_table = $(this).data('result-table');
- var result_table_loadicon = "#" + $(this).data('result-table-loadicon');
-
- // tags which can be ignored
- var osm_ignore_tags = [ "addr:city", "addr:country", "addr:housenumber", "addr:postcode", "addr:street" ];
-
- if(osm_id && osm_type && result_table) {
- result_table = "#" + result_table;
- var query = null;
- switch(osm_type) {
- case 'node':
- query = query_start + "node(" + osm_id + ");" + query_end;
- break;
- case 'way':
- query = query_start + "way(" + osm_id + ");" + query_end;
- break;
- case 'relation':
- query = query_start + "relation(" + osm_id + ");" + query_end;
- break;
- default:
- break;
- }
- if(query) {
- //alert(query);
- var ajaxRequest = $.ajax( query )
- .done(function( html) {
- if(html && html.elements && html.elements[0]) {
- var element = html.elements[0];
- var newHtml = $(result_table).html();
- for (var row in element.tags) {
- if(element.tags.name === null || osm_ignore_tags.indexOf(row) == -1) {
- newHtml += "<tr><td>" + row + "</td><td>";
- switch(row) {
- case "phone":
- case "fax":
- newHtml += "<a href=\"tel:" + element.tags[row].replace(/ /g,'') + "\">" + element.tags[row] + "</a>";
- break;
- case "email":
- newHtml += "<a href=\"mailto:" + element.tags[row] + "\">" + element.tags[row] + "</a>";
- break;
- case "website":
- case "url":
- newHtml += "<a href=\"" + element.tags[row] + "\">" + element.tags[row] + "</a>";
- break;
- case "wikidata":
- newHtml += "<a href=\"https://www.wikidata.org/wiki/" + element.tags[row] + "\">" + element.tags[row] + "</a>";
- break;
- case "wikipedia":
- if(element.tags[row].indexOf(":") != -1) {
- newHtml += "<a href=\"https://" + element.tags[row].substring(0,element.tags[row].indexOf(":")) + ".wikipedia.org/wiki/" + element.tags[row].substring(element.tags[row].indexOf(":")+1) + "\">" + element.tags[row] + "</a>";
- break;
- }
- /* jshint ignore:start */
- default:
- /* jshint ignore:end */
- newHtml += element.tags[row];
- break;
- }
- newHtml += "</td></tr>";
- }
- }
- $(result_table).html(newHtml);
- $(result_table).removeClass('hidden');
- $(result_table_loadicon).addClass('hidden');
- }
- })
- .fail(function() {
- $(result_table_loadicon).html($(result_table_loadicon).html() + "<p class=\"text-muted\">could not load data!</p>");
- });
- }
- }
-
- // this event occour only once per element
- $( this ).off( event );
- });
-
- $(".searx_init_map").on( "click", function( event ) {
- var leaflet_target = $(this).data('leaflet-target');
- var map_lon = $(this).data('map-lon');
- var map_lat = $(this).data('map-lat');
- var map_zoom = $(this).data('map-zoom');
- var map_boundingbox = $(this).data('map-boundingbox');
- var map_geojson = $(this).data('map-geojson');
-
- require(['leaflet-0.7.3.min'], function(leaflet) {
- if(map_boundingbox) {
- southWest = L.latLng(map_boundingbox[0], map_boundingbox[2]);
- northEast = L.latLng(map_boundingbox[1], map_boundingbox[3]);
- map_bounds = L.latLngBounds(southWest, northEast);
- }
-
- // TODO hack
- // change default imagePath
- L.Icon.Default.imagePath = "./static/themes/oscar/img/map";
-
- // init map
- var map = L.map(leaflet_target);
-
- // create the tile layer with correct attribution
- var osmMapnikUrl='https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png';
- var osmMapnikAttrib='Map data © <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 beta | Maps data © <a href="https://openstreetmap.org">OpenStreetMap</a> contributors';
- var osmWikimedia = new L.TileLayer(osmWikimediaUrl, {minZoom: 1, maxZoom: 19, attribution: osmWikimediaAttrib});
-
- // init map view
- if(map_bounds) {
- // TODO hack: https://github.com/Leaflet/Leaflet/issues/2021
- setTimeout(function () {
- map.fitBounds(map_bounds, {
- maxZoom:17
- });
- }, 0);
- } else if (map_lon && map_lat) {
- if(map_zoom)
- map.setView(new L.LatLng(map_lat, map_lon),map_zoom);
- else
- map.setView(new L.LatLng(map_lat, map_lon),8);
- }
-
- map.addLayer(osmMapnik);
-
- var baseLayers = {
- "OSM Mapnik": osmMapnik/*,
- "OSM Wikimedia": osmWikimedia*/
- };
-
- L.control.layers(baseLayers).addTo(map);
-
-
- if(map_geojson)
- L.geoJson(map_geojson).addTo(map);
- /*else if(map_bounds)
- L.rectangle(map_bounds, {color: "#ff7800", weight: 3, fill:false}).addTo(map);*/
- });
-
- // this event occour only once per element
- $( this ).off( event );
- });
-});
+/** + * searx is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * searx is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with searx. If not, see < http://www.gnu.org/licenses/ >. + * + * (C) 2014 by Thomas Pointhuber, <thomas.pointhuber@gmx.at> + */ + +$(document).ready(function(){ + $(".searx_overpass_request").on( "click", function( event ) { + var overpass_url = "https://overpass-api.de/api/interpreter?data="; + var query_start = overpass_url + "[out:json][timeout:25];("; + var query_end = ");out meta;"; + + var osm_id = $(this).data('osm-id'); + var osm_type = $(this).data('osm-type'); + var result_table = $(this).data('result-table'); + var result_table_loadicon = "#" + $(this).data('result-table-loadicon'); + + // tags which can be ignored + var osm_ignore_tags = [ "addr:city", "addr:country", "addr:housenumber", "addr:postcode", "addr:street" ]; + + if(osm_id && osm_type && result_table) { + result_table = "#" + result_table; + var query = null; + switch(osm_type) { + case 'node': + query = query_start + "node(" + osm_id + ");" + query_end; + break; + case 'way': + query = query_start + "way(" + osm_id + ");" + query_end; + break; + case 'relation': + query = query_start + "relation(" + osm_id + ");" + query_end; + break; + default: + break; + } + if(query) { + //alert(query); + var ajaxRequest = $.ajax( query ) + .done(function( html) { + if(html && html.elements && html.elements[0]) { + var element = html.elements[0]; + var newHtml = $(result_table).html(); + for (var row in element.tags) { + if(element.tags.name === null || osm_ignore_tags.indexOf(row) == -1) { + newHtml += "<tr><td>" + row + "</td><td>"; + switch(row) { + case "phone": + case "fax": + newHtml += "<a href=\"tel:" + element.tags[row].replace(/ /g,'') + "\">" + element.tags[row] + "</a>"; + break; + case "email": + newHtml += "<a href=\"mailto:" + element.tags[row] + "\">" + element.tags[row] + "</a>"; + break; + case "website": + case "url": + newHtml += "<a href=\"" + element.tags[row] + "\">" + element.tags[row] + "</a>"; + break; + case "wikidata": + newHtml += "<a href=\"https://www.wikidata.org/wiki/" + element.tags[row] + "\">" + element.tags[row] + "</a>"; + break; + case "wikipedia": + if(element.tags[row].indexOf(":") != -1) { + newHtml += "<a href=\"https://" + element.tags[row].substring(0,element.tags[row].indexOf(":")) + ".wikipedia.org/wiki/" + element.tags[row].substring(element.tags[row].indexOf(":")+1) + "\">" + element.tags[row] + "</a>"; + break; + } + /* jshint ignore:start */ + default: + /* jshint ignore:end */ + newHtml += element.tags[row]; + break; + } + newHtml += "</td></tr>"; + } + } + $(result_table).html(newHtml); + $(result_table).removeClass('hidden'); + $(result_table_loadicon).addClass('hidden'); + } + }) + .fail(function() { + $(result_table_loadicon).html($(result_table_loadicon).html() + "<p class=\"text-muted\">could not load data!</p>"); + }); + } + } + + // this event occour only once per element + $( this ).off( event ); + }); + + $(".searx_init_map").on( "click", function( event ) { + var leaflet_target = $(this).data('leaflet-target'); + var map_lon = $(this).data('map-lon'); + var map_lat = $(this).data('map-lat'); + var map_zoom = $(this).data('map-zoom'); + var map_boundingbox = $(this).data('map-boundingbox'); + var map_geojson = $(this).data('map-geojson'); + + require(['leaflet-0.7.3.min'], function(leaflet) { + if(map_boundingbox) { + southWest = L.latLng(map_boundingbox[0], map_boundingbox[2]); + northEast = L.latLng(map_boundingbox[1], map_boundingbox[3]); + map_bounds = L.latLngBounds(southWest, northEast); + } + + // TODO hack + // change default imagePath + L.Icon.Default.imagePath = "./static/themes/oscar/img/map"; + + // init map + var map = L.map(leaflet_target); + + // create the tile layer with correct attribution + var osmMapnikUrl='https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'; + var osmMapnikAttrib='Map data © <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 beta | Maps data © <a href="https://openstreetmap.org">OpenStreetMap</a> contributors'; + var osmWikimedia = new L.TileLayer(osmWikimediaUrl, {minZoom: 1, maxZoom: 19, attribution: osmWikimediaAttrib}); + + // init map view + if(map_bounds) { + // TODO hack: https://github.com/Leaflet/Leaflet/issues/2021 + setTimeout(function () { + map.fitBounds(map_bounds, { + maxZoom:17 + }); + }, 0); + } else if (map_lon && map_lat) { + if(map_zoom) + map.setView(new L.LatLng(map_lat, map_lon),map_zoom); + else + map.setView(new L.LatLng(map_lat, map_lon),8); + } + + map.addLayer(osmMapnik); + + var baseLayers = { + "OSM Mapnik": osmMapnik/*, + "OSM Wikimedia": osmWikimedia*/ + }; + + L.control.layers(baseLayers).addTo(map); + + + if(map_geojson) + L.geoJson(map_geojson).addTo(map); + /*else if(map_bounds) + L.rectangle(map_bounds, {color: "#ff7800", weight: 3, fill:false}).addTo(map);*/ + }); + + // this event occour only once per element + $( this ).off( event ); + }); +}); diff --git a/searx/static/themes/oscar/less/logicodev-dark/oscar.less b/searx/static/themes/oscar/less/logicodev-dark/oscar.less index 9a4272331..e788b8cba 100644 --- a/searx/static/themes/oscar/less/logicodev-dark/oscar.less +++ b/searx/static/themes/oscar/less/logicodev-dark/oscar.less @@ -109,7 +109,7 @@ ul.nav li a { .btn:hover { color:#444 !important; - background-color: #BBB !important; + background-color: #BBB !important; } .btn-primary.active { @@ -221,7 +221,7 @@ p.btn.btn-default{ } .table-hover > tbody > tr:hover > td, .table-hover > tbody > tr:hover > th { - background: rgb(102, 105, 110) !important; + background: rgb(102, 105, 110) !important; } .btn-success { diff --git a/searx/static/themes/oscar/less/logicodev/code.less b/searx/static/themes/oscar/less/logicodev/code.less index 96486f5aa..491b30e5a 100644 --- a/searx/static/themes/oscar/less/logicodev/code.less +++ b/searx/static/themes/oscar/less/logicodev/code.less @@ -78,7 +78,7 @@ pre, code{ user-select: none; cursor: default; color: #556366; - + &::selection { background: transparent; /* WebKit/Blink Browsers */ } @@ -99,5 +99,3 @@ pre, code{ .highlight { font-weight: 700; } - - diff --git a/searx/static/themes/oscar/less/logicodev/infobox.less b/searx/static/themes/oscar/less/logicodev/infobox.less index 0d488d744..954f4507a 100644 --- a/searx/static/themes/oscar/less/logicodev/infobox.less +++ b/searx/static/themes/oscar/less/logicodev/infobox.less @@ -30,7 +30,7 @@ table-layout: fixed; } - + .infobox_part:last-child { margin-bottom: 0; } diff --git a/searx/static/themes/oscar/less/logicodev/navbar.less b/searx/static/themes/oscar/less/logicodev/navbar.less index 5da7115d9..6e4f9ee10 100644 --- a/searx/static/themes/oscar/less/logicodev/navbar.less +++ b/searx/static/themes/oscar/less/logicodev/navbar.less @@ -28,4 +28,3 @@ width: 80%; } } - diff --git a/searx/static/themes/oscar/less/pointhi/code.less b/searx/static/themes/oscar/less/pointhi/code.less index 90a2cd60c..70a2a5d49 100644 --- a/searx/static/themes/oscar/less/pointhi/code.less +++ b/searx/static/themes/oscar/less/pointhi/code.less @@ -69,7 +69,7 @@ -ms-user-select: none; user-select: none; cursor: default; - + &::selection { background: transparent; /* WebKit/Blink Browsers */ } diff --git a/searx/static/themes/oscar/less/pointhi/infobox.less b/searx/static/themes/oscar/less/pointhi/infobox.less index 41375f277..df51b002e 100644 --- a/searx/static/themes/oscar/less/pointhi/infobox.less +++ b/searx/static/themes/oscar/less/pointhi/infobox.less @@ -4,7 +4,7 @@ word-wrap: break-word; table-layout: fixed; } - + .infobox_part:last-child { margin-bottom: 0; } diff --git a/searx/static/themes/simple/leaflet/leaflet.css b/searx/static/themes/simple/leaflet/leaflet.css index 230e5bad1..d1b47a125 100644 --- a/searx/static/themes/simple/leaflet/leaflet.css +++ b/searx/static/themes/simple/leaflet/leaflet.css @@ -1,636 +1,636 @@ -/* required styles */
-
-.leaflet-pane,
-.leaflet-tile,
-.leaflet-marker-icon,
-.leaflet-marker-shadow,
-.leaflet-tile-container,
-.leaflet-pane > svg,
-.leaflet-pane > canvas,
-.leaflet-zoom-box,
-.leaflet-image-layer,
-.leaflet-layer {
- position: absolute;
- left: 0;
- top: 0;
- }
-.leaflet-container {
- overflow: hidden;
- }
-.leaflet-tile,
-.leaflet-marker-icon,
-.leaflet-marker-shadow {
- -webkit-user-select: none;
- -moz-user-select: none;
- user-select: none;
- -webkit-user-drag: none;
- }
-/* Safari renders non-retina tile on retina better with this, but Chrome is worse */
-.leaflet-safari .leaflet-tile {
- image-rendering: -webkit-optimize-contrast;
- }
-/* hack that prevents hw layers "stretching" when loading new tiles */
-.leaflet-safari .leaflet-tile-container {
- width: 1600px;
- height: 1600px;
- -webkit-transform-origin: 0 0;
- }
-.leaflet-marker-icon,
-.leaflet-marker-shadow {
- display: block;
- }
-/* .leaflet-container svg: reset svg max-width decleration shipped in Joomla! (joomla.org) 3.x */
-/* .leaflet-container img: map is broken in FF if you have max-width: 100% on tiles */
-.leaflet-container .leaflet-overlay-pane svg,
-.leaflet-container .leaflet-marker-pane img,
-.leaflet-container .leaflet-shadow-pane img,
-.leaflet-container .leaflet-tile-pane img,
-.leaflet-container img.leaflet-image-layer {
- max-width: none !important;
- max-height: none !important;
- }
-
-.leaflet-container.leaflet-touch-zoom {
- -ms-touch-action: pan-x pan-y;
- touch-action: pan-x pan-y;
- }
-.leaflet-container.leaflet-touch-drag {
- -ms-touch-action: pinch-zoom;
- /* Fallback for FF which doesn't support pinch-zoom */
- touch-action: none;
- touch-action: pinch-zoom;
-}
-.leaflet-container.leaflet-touch-drag.leaflet-touch-zoom {
- -ms-touch-action: none;
- touch-action: none;
-}
-.leaflet-container {
- -webkit-tap-highlight-color: transparent;
-}
-.leaflet-container a {
- -webkit-tap-highlight-color: rgba(51, 181, 229, 0.4);
-}
-.leaflet-tile {
- filter: inherit;
- visibility: hidden;
- }
-.leaflet-tile-loaded {
- visibility: inherit;
- }
-.leaflet-zoom-box {
- width: 0;
- height: 0;
- -moz-box-sizing: border-box;
- box-sizing: border-box;
- z-index: 800;
- }
-/* workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=888319 */
-.leaflet-overlay-pane svg {
- -moz-user-select: none;
- }
-
-.leaflet-pane { z-index: 400; }
-
-.leaflet-tile-pane { z-index: 200; }
-.leaflet-overlay-pane { z-index: 400; }
-.leaflet-shadow-pane { z-index: 500; }
-.leaflet-marker-pane { z-index: 600; }
-.leaflet-tooltip-pane { z-index: 650; }
-.leaflet-popup-pane { z-index: 700; }
-
-.leaflet-map-pane canvas { z-index: 100; }
-.leaflet-map-pane svg { z-index: 200; }
-
-.leaflet-vml-shape {
- width: 1px;
- height: 1px;
- }
-.lvml {
- behavior: url(#default#VML);
- display: inline-block;
- position: absolute;
- }
-
-
-/* control positioning */
-
-.leaflet-control {
- position: relative;
- z-index: 800;
- pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */
- pointer-events: auto;
- }
-.leaflet-top,
-.leaflet-bottom {
- position: absolute;
- z-index: 1000;
- pointer-events: none;
- }
-.leaflet-top {
- top: 0;
- }
-.leaflet-right {
- right: 0;
- }
-.leaflet-bottom {
- bottom: 0;
- }
-.leaflet-left {
- left: 0;
- }
-.leaflet-control {
- float: left;
- clear: both;
- }
-.leaflet-right .leaflet-control {
- float: right;
- }
-.leaflet-top .leaflet-control {
- margin-top: 10px;
- }
-.leaflet-bottom .leaflet-control {
- margin-bottom: 10px;
- }
-.leaflet-left .leaflet-control {
- margin-left: 10px;
- }
-.leaflet-right .leaflet-control {
- margin-right: 10px;
- }
-
-
-/* zoom and fade animations */
-
-.leaflet-fade-anim .leaflet-tile {
- will-change: opacity;
- }
-.leaflet-fade-anim .leaflet-popup {
- opacity: 0;
- -webkit-transition: opacity 0.2s linear;
- -moz-transition: opacity 0.2s linear;
- -o-transition: opacity 0.2s linear;
- transition: opacity 0.2s linear;
- }
-.leaflet-fade-anim .leaflet-map-pane .leaflet-popup {
- opacity: 1;
- }
-.leaflet-zoom-animated {
- -webkit-transform-origin: 0 0;
- -ms-transform-origin: 0 0;
- transform-origin: 0 0;
- }
-.leaflet-zoom-anim .leaflet-zoom-animated {
- will-change: transform;
- }
-.leaflet-zoom-anim .leaflet-zoom-animated {
- -webkit-transition: -webkit-transform 0.25s cubic-bezier(0,0,0.25,1);
- -moz-transition: -moz-transform 0.25s cubic-bezier(0,0,0.25,1);
- -o-transition: -o-transform 0.25s cubic-bezier(0,0,0.25,1);
- transition: transform 0.25s cubic-bezier(0,0,0.25,1);
- }
-.leaflet-zoom-anim .leaflet-tile,
-.leaflet-pan-anim .leaflet-tile {
- -webkit-transition: none;
- -moz-transition: none;
- -o-transition: none;
- transition: none;
- }
-
-.leaflet-zoom-anim .leaflet-zoom-hide {
- visibility: hidden;
- }
-
-
-/* cursors */
-
-.leaflet-interactive {
- cursor: pointer;
- }
-.leaflet-grab {
- cursor: -webkit-grab;
- cursor: -moz-grab;
- }
-.leaflet-crosshair,
-.leaflet-crosshair .leaflet-interactive {
- cursor: crosshair;
- }
-.leaflet-popup-pane,
-.leaflet-control {
- cursor: auto;
- }
-.leaflet-dragging .leaflet-grab,
-.leaflet-dragging .leaflet-grab .leaflet-interactive,
-.leaflet-dragging .leaflet-marker-draggable {
- cursor: move;
- cursor: -webkit-grabbing;
- cursor: -moz-grabbing;
- }
-
-/* marker & overlays interactivity */
-.leaflet-marker-icon,
-.leaflet-marker-shadow,
-.leaflet-image-layer,
-.leaflet-pane > svg path,
-.leaflet-tile-container {
- pointer-events: none;
- }
-
-.leaflet-marker-icon.leaflet-interactive,
-.leaflet-image-layer.leaflet-interactive,
-.leaflet-pane > svg path.leaflet-interactive {
- pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */
- pointer-events: auto;
- }
-
-/* visual tweaks */
-
-.leaflet-container {
- background: #ddd;
- outline: 0;
- }
-.leaflet-container a {
- color: #0078A8;
- }
-.leaflet-container a.leaflet-active {
- outline: 2px solid orange;
- }
-.leaflet-zoom-box {
- border: 2px dotted #38f;
- background: rgba(255,255,255,0.5);
- }
-
-
-/* general typography */
-.leaflet-container {
- font: 12px/1.5 "Helvetica Neue", Arial, Helvetica, sans-serif;
- }
-
-
-/* general toolbar styles */
-
-.leaflet-bar {
- box-shadow: 0 1px 5px rgba(0,0,0,0.65);
- border-radius: 4px;
- }
-.leaflet-bar a,
-.leaflet-bar a:hover {
- background-color: #fff;
- border-bottom: 1px solid #ccc;
- width: 26px;
- height: 26px;
- line-height: 26px;
- display: block;
- text-align: center;
- text-decoration: none;
- color: black;
- }
-.leaflet-bar a,
-.leaflet-control-layers-toggle {
- background-position: 50% 50%;
- background-repeat: no-repeat;
- display: block;
- }
-.leaflet-bar a:hover {
- background-color: #f4f4f4;
- }
-.leaflet-bar a:first-child {
- border-top-left-radius: 4px;
- border-top-right-radius: 4px;
- }
-.leaflet-bar a:last-child {
- border-bottom-left-radius: 4px;
- border-bottom-right-radius: 4px;
- border-bottom: none;
- }
-.leaflet-bar a.leaflet-disabled {
- cursor: default;
- background-color: #f4f4f4;
- color: #bbb;
- }
-
-.leaflet-touch .leaflet-bar a {
- width: 30px;
- height: 30px;
- line-height: 30px;
- }
-.leaflet-touch .leaflet-bar a:first-child {
- border-top-left-radius: 2px;
- border-top-right-radius: 2px;
- }
-.leaflet-touch .leaflet-bar a:last-child {
- border-bottom-left-radius: 2px;
- border-bottom-right-radius: 2px;
- }
-
-/* zoom control */
-
-.leaflet-control-zoom-in,
-.leaflet-control-zoom-out {
- font: bold 18px 'Lucida Console', Monaco, monospace;
- text-indent: 1px;
- }
-
-.leaflet-touch .leaflet-control-zoom-in, .leaflet-touch .leaflet-control-zoom-out {
- font-size: 22px;
- }
-
-
-/* layers control */
-
-.leaflet-control-layers {
- box-shadow: 0 1px 5px rgba(0,0,0,0.4);
- background: #fff;
- border-radius: 5px;
- }
-.leaflet-control-layers-toggle {
- background-image: url(images/layers.png);
- width: 36px;
- height: 36px;
- }
-.leaflet-retina .leaflet-control-layers-toggle {
- background-image: url(images/layers-2x.png);
- background-size: 26px 26px;
- }
-.leaflet-touch .leaflet-control-layers-toggle {
- width: 44px;
- height: 44px;
- }
-.leaflet-control-layers .leaflet-control-layers-list,
-.leaflet-control-layers-expanded .leaflet-control-layers-toggle {
- display: none;
- }
-.leaflet-control-layers-expanded .leaflet-control-layers-list {
- display: block;
- position: relative;
- }
-.leaflet-control-layers-expanded {
- padding: 6px 10px 6px 6px;
- color: #333;
- background: #fff;
- }
-.leaflet-control-layers-scrollbar {
- overflow-y: scroll;
- overflow-x: hidden;
- padding-right: 5px;
- }
-.leaflet-control-layers-selector {
- margin-top: 2px;
- position: relative;
- top: 1px;
- }
-.leaflet-control-layers label {
- display: block;
- }
-.leaflet-control-layers-separator {
- height: 0;
- border-top: 1px solid #ddd;
- margin: 5px -10px 5px -6px;
- }
-
-/* Default icon URLs */
-.leaflet-default-icon-path {
- background-image: url(images/marker-icon.png);
- }
-
-
-/* attribution and scale controls */
-
-.leaflet-container .leaflet-control-attribution {
- background: #fff;
- background: rgba(255, 255, 255, 0.7);
- margin: 0;
- }
-.leaflet-control-attribution,
-.leaflet-control-scale-line {
- padding: 0 5px;
- color: #333;
- }
-.leaflet-control-attribution a {
- text-decoration: none;
- }
-.leaflet-control-attribution a:hover {
- text-decoration: underline;
- }
-.leaflet-container .leaflet-control-attribution,
-.leaflet-container .leaflet-control-scale {
- font-size: 11px;
- }
-.leaflet-left .leaflet-control-scale {
- margin-left: 5px;
- }
-.leaflet-bottom .leaflet-control-scale {
- margin-bottom: 5px;
- }
-.leaflet-control-scale-line {
- border: 2px solid #777;
- border-top: none;
- line-height: 1.1;
- padding: 2px 5px 1px;
- font-size: 11px;
- white-space: nowrap;
- overflow: hidden;
- -moz-box-sizing: border-box;
- box-sizing: border-box;
-
- background: #fff;
- background: rgba(255, 255, 255, 0.5);
- }
-.leaflet-control-scale-line:not(:first-child) {
- border-top: 2px solid #777;
- border-bottom: none;
- margin-top: -2px;
- }
-.leaflet-control-scale-line:not(:first-child):not(:last-child) {
- border-bottom: 2px solid #777;
- }
-
-.leaflet-touch .leaflet-control-attribution,
-.leaflet-touch .leaflet-control-layers,
-.leaflet-touch .leaflet-bar {
- box-shadow: none;
- }
-.leaflet-touch .leaflet-control-layers,
-.leaflet-touch .leaflet-bar {
- border: 2px solid rgba(0,0,0,0.2);
- background-clip: padding-box;
- }
-
-
-/* popup */
-
-.leaflet-popup {
- position: absolute;
- text-align: center;
- margin-bottom: 20px;
- }
-.leaflet-popup-content-wrapper {
- padding: 1px;
- text-align: left;
- border-radius: 12px;
- }
-.leaflet-popup-content {
- margin: 13px 19px;
- line-height: 1.4;
- }
-.leaflet-popup-content p {
- margin: 18px 0;
- }
-.leaflet-popup-tip-container {
- width: 40px;
- height: 20px;
- position: absolute;
- left: 50%;
- margin-left: -20px;
- overflow: hidden;
- pointer-events: none;
- }
-.leaflet-popup-tip {
- width: 17px;
- height: 17px;
- padding: 1px;
-
- margin: -10px auto 0;
-
- -webkit-transform: rotate(45deg);
- -moz-transform: rotate(45deg);
- -ms-transform: rotate(45deg);
- -o-transform: rotate(45deg);
- transform: rotate(45deg);
- }
-.leaflet-popup-content-wrapper,
-.leaflet-popup-tip {
- background: white;
- color: #333;
- box-shadow: 0 3px 14px rgba(0,0,0,0.4);
- }
-.leaflet-container a.leaflet-popup-close-button {
- position: absolute;
- top: 0;
- right: 0;
- padding: 4px 4px 0 0;
- border: none;
- text-align: center;
- width: 18px;
- height: 14px;
- font: 16px/14px Tahoma, Verdana, sans-serif;
- color: #c3c3c3;
- text-decoration: none;
- font-weight: bold;
- background: transparent;
- }
-.leaflet-container a.leaflet-popup-close-button:hover {
- color: #999;
- }
-.leaflet-popup-scrolled {
- overflow: auto;
- border-bottom: 1px solid #ddd;
- border-top: 1px solid #ddd;
- }
-
-.leaflet-oldie .leaflet-popup-content-wrapper {
- zoom: 1;
- }
-.leaflet-oldie .leaflet-popup-tip {
- width: 24px;
- margin: 0 auto;
-
- -ms-filter: "progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678)";
- filter: progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678);
- }
-.leaflet-oldie .leaflet-popup-tip-container {
- margin-top: -1px;
- }
-
-.leaflet-oldie .leaflet-control-zoom,
-.leaflet-oldie .leaflet-control-layers,
-.leaflet-oldie .leaflet-popup-content-wrapper,
-.leaflet-oldie .leaflet-popup-tip {
- border: 1px solid #999;
- }
-
-
-/* div icon */
-
-.leaflet-div-icon {
- background: #fff;
- border: 1px solid #666;
- }
-
-
-/* Tooltip */
-/* Base styles for the element that has a tooltip */
-.leaflet-tooltip {
- position: absolute;
- padding: 6px;
- background-color: #fff;
- border: 1px solid #fff;
- border-radius: 3px;
- color: #222;
- white-space: nowrap;
- -webkit-user-select: none;
- -moz-user-select: none;
- -ms-user-select: none;
- user-select: none;
- pointer-events: none;
- box-shadow: 0 1px 3px rgba(0,0,0,0.4);
- }
-.leaflet-tooltip.leaflet-clickable {
- cursor: pointer;
- pointer-events: auto;
- }
-.leaflet-tooltip-top:before,
-.leaflet-tooltip-bottom:before,
-.leaflet-tooltip-left:before,
-.leaflet-tooltip-right:before {
- position: absolute;
- pointer-events: none;
- border: 6px solid transparent;
- background: transparent;
- content: "";
- }
-
-/* Directions */
-
-.leaflet-tooltip-bottom {
- margin-top: 6px;
-}
-.leaflet-tooltip-top {
- margin-top: -6px;
-}
-.leaflet-tooltip-bottom:before,
-.leaflet-tooltip-top:before {
- left: 50%;
- margin-left: -6px;
- }
-.leaflet-tooltip-top:before {
- bottom: 0;
- margin-bottom: -12px;
- border-top-color: #fff;
- }
-.leaflet-tooltip-bottom:before {
- top: 0;
- margin-top: -12px;
- margin-left: -6px;
- border-bottom-color: #fff;
- }
-.leaflet-tooltip-left {
- margin-left: -6px;
-}
-.leaflet-tooltip-right {
- margin-left: 6px;
-}
-.leaflet-tooltip-left:before,
-.leaflet-tooltip-right:before {
- top: 50%;
- margin-top: -6px;
- }
-.leaflet-tooltip-left:before {
- right: 0;
- margin-right: -12px;
- border-left-color: #fff;
- }
-.leaflet-tooltip-right:before {
- left: 0;
- margin-left: -12px;
- border-right-color: #fff;
- }
+/* required styles */ + +.leaflet-pane, +.leaflet-tile, +.leaflet-marker-icon, +.leaflet-marker-shadow, +.leaflet-tile-container, +.leaflet-pane > svg, +.leaflet-pane > canvas, +.leaflet-zoom-box, +.leaflet-image-layer, +.leaflet-layer { + position: absolute; + left: 0; + top: 0; + } +.leaflet-container { + overflow: hidden; + } +.leaflet-tile, +.leaflet-marker-icon, +.leaflet-marker-shadow { + -webkit-user-select: none; + -moz-user-select: none; + user-select: none; + -webkit-user-drag: none; + } +/* Safari renders non-retina tile on retina better with this, but Chrome is worse */ +.leaflet-safari .leaflet-tile { + image-rendering: -webkit-optimize-contrast; + } +/* hack that prevents hw layers "stretching" when loading new tiles */ +.leaflet-safari .leaflet-tile-container { + width: 1600px; + height: 1600px; + -webkit-transform-origin: 0 0; + } +.leaflet-marker-icon, +.leaflet-marker-shadow { + display: block; + } +/* .leaflet-container svg: reset svg max-width decleration shipped in Joomla! (joomla.org) 3.x */ +/* .leaflet-container img: map is broken in FF if you have max-width: 100% on tiles */ +.leaflet-container .leaflet-overlay-pane svg, +.leaflet-container .leaflet-marker-pane img, +.leaflet-container .leaflet-shadow-pane img, +.leaflet-container .leaflet-tile-pane img, +.leaflet-container img.leaflet-image-layer { + max-width: none !important; + max-height: none !important; + } + +.leaflet-container.leaflet-touch-zoom { + -ms-touch-action: pan-x pan-y; + touch-action: pan-x pan-y; + } +.leaflet-container.leaflet-touch-drag { + -ms-touch-action: pinch-zoom; + /* Fallback for FF which doesn't support pinch-zoom */ + touch-action: none; + touch-action: pinch-zoom; +} +.leaflet-container.leaflet-touch-drag.leaflet-touch-zoom { + -ms-touch-action: none; + touch-action: none; +} +.leaflet-container { + -webkit-tap-highlight-color: transparent; +} +.leaflet-container a { + -webkit-tap-highlight-color: rgba(51, 181, 229, 0.4); +} +.leaflet-tile { + filter: inherit; + visibility: hidden; + } +.leaflet-tile-loaded { + visibility: inherit; + } +.leaflet-zoom-box { + width: 0; + height: 0; + -moz-box-sizing: border-box; + box-sizing: border-box; + z-index: 800; + } +/* workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=888319 */ +.leaflet-overlay-pane svg { + -moz-user-select: none; + } + +.leaflet-pane { z-index: 400; } + +.leaflet-tile-pane { z-index: 200; } +.leaflet-overlay-pane { z-index: 400; } +.leaflet-shadow-pane { z-index: 500; } +.leaflet-marker-pane { z-index: 600; } +.leaflet-tooltip-pane { z-index: 650; } +.leaflet-popup-pane { z-index: 700; } + +.leaflet-map-pane canvas { z-index: 100; } +.leaflet-map-pane svg { z-index: 200; } + +.leaflet-vml-shape { + width: 1px; + height: 1px; + } +.lvml { + behavior: url(#default#VML); + display: inline-block; + position: absolute; + } + + +/* control positioning */ + +.leaflet-control { + position: relative; + z-index: 800; + pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */ + pointer-events: auto; + } +.leaflet-top, +.leaflet-bottom { + position: absolute; + z-index: 1000; + pointer-events: none; + } +.leaflet-top { + top: 0; + } +.leaflet-right { + right: 0; + } +.leaflet-bottom { + bottom: 0; + } +.leaflet-left { + left: 0; + } +.leaflet-control { + float: left; + clear: both; + } +.leaflet-right .leaflet-control { + float: right; + } +.leaflet-top .leaflet-control { + margin-top: 10px; + } +.leaflet-bottom .leaflet-control { + margin-bottom: 10px; + } +.leaflet-left .leaflet-control { + margin-left: 10px; + } +.leaflet-right .leaflet-control { + margin-right: 10px; + } + + +/* zoom and fade animations */ + +.leaflet-fade-anim .leaflet-tile { + will-change: opacity; + } +.leaflet-fade-anim .leaflet-popup { + opacity: 0; + -webkit-transition: opacity 0.2s linear; + -moz-transition: opacity 0.2s linear; + -o-transition: opacity 0.2s linear; + transition: opacity 0.2s linear; + } +.leaflet-fade-anim .leaflet-map-pane .leaflet-popup { + opacity: 1; + } +.leaflet-zoom-animated { + -webkit-transform-origin: 0 0; + -ms-transform-origin: 0 0; + transform-origin: 0 0; + } +.leaflet-zoom-anim .leaflet-zoom-animated { + will-change: transform; + } +.leaflet-zoom-anim .leaflet-zoom-animated { + -webkit-transition: -webkit-transform 0.25s cubic-bezier(0,0,0.25,1); + -moz-transition: -moz-transform 0.25s cubic-bezier(0,0,0.25,1); + -o-transition: -o-transform 0.25s cubic-bezier(0,0,0.25,1); + transition: transform 0.25s cubic-bezier(0,0,0.25,1); + } +.leaflet-zoom-anim .leaflet-tile, +.leaflet-pan-anim .leaflet-tile { + -webkit-transition: none; + -moz-transition: none; + -o-transition: none; + transition: none; + } + +.leaflet-zoom-anim .leaflet-zoom-hide { + visibility: hidden; + } + + +/* cursors */ + +.leaflet-interactive { + cursor: pointer; + } +.leaflet-grab { + cursor: -webkit-grab; + cursor: -moz-grab; + } +.leaflet-crosshair, +.leaflet-crosshair .leaflet-interactive { + cursor: crosshair; + } +.leaflet-popup-pane, +.leaflet-control { + cursor: auto; + } +.leaflet-dragging .leaflet-grab, +.leaflet-dragging .leaflet-grab .leaflet-interactive, +.leaflet-dragging .leaflet-marker-draggable { + cursor: move; + cursor: -webkit-grabbing; + cursor: -moz-grabbing; + } + +/* marker & overlays interactivity */ +.leaflet-marker-icon, +.leaflet-marker-shadow, +.leaflet-image-layer, +.leaflet-pane > svg path, +.leaflet-tile-container { + pointer-events: none; + } + +.leaflet-marker-icon.leaflet-interactive, +.leaflet-image-layer.leaflet-interactive, +.leaflet-pane > svg path.leaflet-interactive { + pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */ + pointer-events: auto; + } + +/* visual tweaks */ + +.leaflet-container { + background: #ddd; + outline: 0; + } +.leaflet-container a { + color: #0078A8; + } +.leaflet-container a.leaflet-active { + outline: 2px solid orange; + } +.leaflet-zoom-box { + border: 2px dotted #38f; + background: rgba(255,255,255,0.5); + } + + +/* general typography */ +.leaflet-container { + font: 12px/1.5 "Helvetica Neue", Arial, Helvetica, sans-serif; + } + + +/* general toolbar styles */ + +.leaflet-bar { + box-shadow: 0 1px 5px rgba(0,0,0,0.65); + border-radius: 4px; + } +.leaflet-bar a, +.leaflet-bar a:hover { + background-color: #fff; + border-bottom: 1px solid #ccc; + width: 26px; + height: 26px; + line-height: 26px; + display: block; + text-align: center; + text-decoration: none; + color: black; + } +.leaflet-bar a, +.leaflet-control-layers-toggle { + background-position: 50% 50%; + background-repeat: no-repeat; + display: block; + } +.leaflet-bar a:hover { + background-color: #f4f4f4; + } +.leaflet-bar a:first-child { + border-top-left-radius: 4px; + border-top-right-radius: 4px; + } +.leaflet-bar a:last-child { + border-bottom-left-radius: 4px; + border-bottom-right-radius: 4px; + border-bottom: none; + } +.leaflet-bar a.leaflet-disabled { + cursor: default; + background-color: #f4f4f4; + color: #bbb; + } + +.leaflet-touch .leaflet-bar a { + width: 30px; + height: 30px; + line-height: 30px; + } +.leaflet-touch .leaflet-bar a:first-child { + border-top-left-radius: 2px; + border-top-right-radius: 2px; + } +.leaflet-touch .leaflet-bar a:last-child { + border-bottom-left-radius: 2px; + border-bottom-right-radius: 2px; + } + +/* zoom control */ + +.leaflet-control-zoom-in, +.leaflet-control-zoom-out { + font: bold 18px 'Lucida Console', Monaco, monospace; + text-indent: 1px; + } + +.leaflet-touch .leaflet-control-zoom-in, .leaflet-touch .leaflet-control-zoom-out { + font-size: 22px; + } + + +/* layers control */ + +.leaflet-control-layers { + box-shadow: 0 1px 5px rgba(0,0,0,0.4); + background: #fff; + border-radius: 5px; + } +.leaflet-control-layers-toggle { + background-image: url(images/layers.png); + width: 36px; + height: 36px; + } +.leaflet-retina .leaflet-control-layers-toggle { + background-image: url(images/layers-2x.png); + background-size: 26px 26px; + } +.leaflet-touch .leaflet-control-layers-toggle { + width: 44px; + height: 44px; + } +.leaflet-control-layers .leaflet-control-layers-list, +.leaflet-control-layers-expanded .leaflet-control-layers-toggle { + display: none; + } +.leaflet-control-layers-expanded .leaflet-control-layers-list { + display: block; + position: relative; + } +.leaflet-control-layers-expanded { + padding: 6px 10px 6px 6px; + color: #333; + background: #fff; + } +.leaflet-control-layers-scrollbar { + overflow-y: scroll; + overflow-x: hidden; + padding-right: 5px; + } +.leaflet-control-layers-selector { + margin-top: 2px; + position: relative; + top: 1px; + } +.leaflet-control-layers label { + display: block; + } +.leaflet-control-layers-separator { + height: 0; + border-top: 1px solid #ddd; + margin: 5px -10px 5px -6px; + } + +/* Default icon URLs */ +.leaflet-default-icon-path { + background-image: url(images/marker-icon.png); + } + + +/* attribution and scale controls */ + +.leaflet-container .leaflet-control-attribution { + background: #fff; + background: rgba(255, 255, 255, 0.7); + margin: 0; + } +.leaflet-control-attribution, +.leaflet-control-scale-line { + padding: 0 5px; + color: #333; + } +.leaflet-control-attribution a { + text-decoration: none; + } +.leaflet-control-attribution a:hover { + text-decoration: underline; + } +.leaflet-container .leaflet-control-attribution, +.leaflet-container .leaflet-control-scale { + font-size: 11px; + } +.leaflet-left .leaflet-control-scale { + margin-left: 5px; + } +.leaflet-bottom .leaflet-control-scale { + margin-bottom: 5px; + } +.leaflet-control-scale-line { + border: 2px solid #777; + border-top: none; + line-height: 1.1; + padding: 2px 5px 1px; + font-size: 11px; + white-space: nowrap; + overflow: hidden; + -moz-box-sizing: border-box; + box-sizing: border-box; + + background: #fff; + background: rgba(255, 255, 255, 0.5); + } +.leaflet-control-scale-line:not(:first-child) { + border-top: 2px solid #777; + border-bottom: none; + margin-top: -2px; + } +.leaflet-control-scale-line:not(:first-child):not(:last-child) { + border-bottom: 2px solid #777; + } + +.leaflet-touch .leaflet-control-attribution, +.leaflet-touch .leaflet-control-layers, +.leaflet-touch .leaflet-bar { + box-shadow: none; + } +.leaflet-touch .leaflet-control-layers, +.leaflet-touch .leaflet-bar { + border: 2px solid rgba(0,0,0,0.2); + background-clip: padding-box; + } + + +/* popup */ + +.leaflet-popup { + position: absolute; + text-align: center; + margin-bottom: 20px; + } +.leaflet-popup-content-wrapper { + padding: 1px; + text-align: left; + border-radius: 12px; + } +.leaflet-popup-content { + margin: 13px 19px; + line-height: 1.4; + } +.leaflet-popup-content p { + margin: 18px 0; + } +.leaflet-popup-tip-container { + width: 40px; + height: 20px; + position: absolute; + left: 50%; + margin-left: -20px; + overflow: hidden; + pointer-events: none; + } +.leaflet-popup-tip { + width: 17px; + height: 17px; + padding: 1px; + + margin: -10px auto 0; + + -webkit-transform: rotate(45deg); + -moz-transform: rotate(45deg); + -ms-transform: rotate(45deg); + -o-transform: rotate(45deg); + transform: rotate(45deg); + } +.leaflet-popup-content-wrapper, +.leaflet-popup-tip { + background: white; + color: #333; + box-shadow: 0 3px 14px rgba(0,0,0,0.4); + } +.leaflet-container a.leaflet-popup-close-button { + position: absolute; + top: 0; + right: 0; + padding: 4px 4px 0 0; + border: none; + text-align: center; + width: 18px; + height: 14px; + font: 16px/14px Tahoma, Verdana, sans-serif; + color: #c3c3c3; + text-decoration: none; + font-weight: bold; + background: transparent; + } +.leaflet-container a.leaflet-popup-close-button:hover { + color: #999; + } +.leaflet-popup-scrolled { + overflow: auto; + border-bottom: 1px solid #ddd; + border-top: 1px solid #ddd; + } + +.leaflet-oldie .leaflet-popup-content-wrapper { + zoom: 1; + } +.leaflet-oldie .leaflet-popup-tip { + width: 24px; + margin: 0 auto; + + -ms-filter: "progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678)"; + filter: progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678); + } +.leaflet-oldie .leaflet-popup-tip-container { + margin-top: -1px; + } + +.leaflet-oldie .leaflet-control-zoom, +.leaflet-oldie .leaflet-control-layers, +.leaflet-oldie .leaflet-popup-content-wrapper, +.leaflet-oldie .leaflet-popup-tip { + border: 1px solid #999; + } + + +/* div icon */ + +.leaflet-div-icon { + background: #fff; + border: 1px solid #666; + } + + +/* Tooltip */ +/* Base styles for the element that has a tooltip */ +.leaflet-tooltip { + position: absolute; + padding: 6px; + background-color: #fff; + border: 1px solid #fff; + border-radius: 3px; + color: #222; + white-space: nowrap; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + pointer-events: none; + box-shadow: 0 1px 3px rgba(0,0,0,0.4); + } +.leaflet-tooltip.leaflet-clickable { + cursor: pointer; + pointer-events: auto; + } +.leaflet-tooltip-top:before, +.leaflet-tooltip-bottom:before, +.leaflet-tooltip-left:before, +.leaflet-tooltip-right:before { + position: absolute; + pointer-events: none; + border: 6px solid transparent; + background: transparent; + content: ""; + } + +/* Directions */ + +.leaflet-tooltip-bottom { + margin-top: 6px; +} +.leaflet-tooltip-top { + margin-top: -6px; +} +.leaflet-tooltip-bottom:before, +.leaflet-tooltip-top:before { + left: 50%; + margin-left: -6px; + } +.leaflet-tooltip-top:before { + bottom: 0; + margin-bottom: -12px; + border-top-color: #fff; + } +.leaflet-tooltip-bottom:before { + top: 0; + margin-top: -12px; + margin-left: -6px; + border-bottom-color: #fff; + } +.leaflet-tooltip-left { + margin-left: -6px; +} +.leaflet-tooltip-right { + margin-left: 6px; +} +.leaflet-tooltip-left:before, +.leaflet-tooltip-right:before { + top: 50%; + margin-top: -6px; + } +.leaflet-tooltip-left:before { + right: 0; + margin-right: -12px; + border-left-color: #fff; + } +.leaflet-tooltip-right:before { + left: 0; + margin-left: -12px; + border-right-color: #fff; + } diff --git a/searx/templates/courgette/result_templates/torrent.html b/searx/templates/courgette/result_templates/torrent.html index d659064d9..7f94a221e 100644 --- a/searx/templates/courgette/result_templates/torrent.html +++ b/searx/templates/courgette/result_templates/torrent.html @@ -4,7 +4,7 @@ {% endif %} <h3 class="result_title"><a href="{{ result.url }}" {% if results_on_new_tab %}target="_blank" rel="noopener noreferrer"{% else %}rel="noreferrer"{% endif %}>{{ result.title|safe }}</a></h3> {% if result.content %}<span class="content">{{ result.content|safe }}</span><br />{% endif %} - {% if result.seed %}<span class="stats">{{ _('Seeder') }} : {{ result.seed }}, {{ _('Leecher') }} : {{ result.leech }}</span><br />{% endif %} + {% if result.seed is defined %}<span class="stats">{{ _('Seeder') }} : {{ result.seed }}, {{ _('Leecher') }} : {{ result.leech }}</span><br />{% endif %} <span> {% if result.magnetlink %}<a href="{{ result.magnetlink }}" class="magnetlink">{{ _('magnet link') }}</a>{% endif %} {% if result.torrentfile %}<a href="{{ result.torrentfile }}" class="torrentfile" {% if results_on_new_tab %}target="_blank" rel="noopener noreferrer"{% else %}rel="noreferrer"{% endif %}>{{ _('torrent file') }}</a>{% endif %} diff --git a/searx/templates/legacy/result_templates/torrent.html b/searx/templates/legacy/result_templates/torrent.html index 7a8ac33de..068e05373 100644 --- a/searx/templates/legacy/result_templates/torrent.html +++ b/searx/templates/legacy/result_templates/torrent.html @@ -8,6 +8,6 @@ <p> {% if result.magnetlink %}<a href="{{ result.magnetlink }}" class="magnetlink">{{ _('magnet link') }}</a>{% endif %} {% if result.torrentfile %}<a href="{{ result.torrentfile }}" {% if results_on_new_tab %}target="_blank" rel="noopener noreferrer"{% else %}rel="noreferrer"{% endif %} class="torrentfile">{{ _('torrent file') }}</a>{% endif %} - - {% if result.seed %}<span class="stats">{{ _('Seeder') }} : {{ result.seed }}, {{ _('Leecher') }} : {{ result.leech }}</span>{% endif %} + {% if result.seed is defined %}<span class="stats">{{ _('Seeder') }} : {{ result.seed }}, {{ _('Leecher') }} : {{ result.leech }}</span>{% endif %} </p> </div> diff --git a/searx/templates/oscar/advanced.html b/searx/templates/oscar/advanced.html index 95d99ba6a..bf5f86324 100644 --- a/searx/templates/oscar/advanced.html +++ b/searx/templates/oscar/advanced.html @@ -1,16 +1,17 @@ <input type="checkbox" name="advanced_search" id="check-advanced" {% if advanced_search %} checked="checked"{% endif %}> -<label for="check-advanced"> +<label for="check-advanced">{{- "" -}} <span class="glyphicon glyphicon-cog"></span> - {{ _('Advanced settings') }} + {{- _('Advanced settings') -}} </label> <div id="advanced-search-container"> {% include 'oscar/categories.html' %} + <div class="row"> <div class="col-xs-6"> - {% include 'oscar/time-range.html' %} + {%- include 'oscar/time-range.html' -%} </div> <div class="col-xs-6"> - {% include 'oscar/languages.html' %} + {%- include 'oscar/languages.html' -%} </div> </div> </div> diff --git a/searx/templates/oscar/base.html b/searx/templates/oscar/base.html index 321784ebb..66a9e6029 100644 --- a/searx/templates/oscar/base.html +++ b/searx/templates/oscar/base.html @@ -10,16 +10,17 @@ <meta name="referrer" content="no-referrer"> <meta name="viewport" content="width=device-width, initial-scale=1 , maximum-scale=1.0, user-scalable=1" /> {% block meta %}{% endblock %} - <title>{% block title %}{% endblock %}{{ instance_name }}</title> + <title>{% block title %}{% endblock %}{{ instance_name }}</title> <link rel="stylesheet" href="{{ url_for('static', filename='css/bootstrap.min.css') }}" type="text/css" /> - {% if preferences.get_value('oscar-style') %} - <link rel="stylesheet" href="{{ url_for('static', filename='css/'+preferences.get_value('oscar-style')+'.min.css') }}" type="text/css" /> - {% else %} - <link rel="stylesheet" href="{{ url_for('static', filename='css/logicodev.min.css') }}" type="text/css" /> - {% endif %} + {% if preferences.get_value('oscar-style') -%} + {{' '}}<link rel="stylesheet" href="{{ url_for('static', filename='css/'+preferences.get_value('oscar-style')+'.min.css') }}" type="text/css" /> + {%- else -%} + {{' '}}<link rel="stylesheet" href="{{ url_for('static', filename='css/logicodev.min.css') }}" type="text/css" /> + {%- endif %} + <link rel="stylesheet" href="{{ url_for('static', filename='css/leaflet.min.css') }}" type="text/css" /> - {% for css in styles %} + {%- for css in styles %} <link rel="stylesheet" href="{{ url_for('static', filename=css) }}" type="text/css" /> {% endfor %} @@ -48,6 +49,7 @@ </head> <body> {% include 'oscar/navbar.html' %} + <div class="container"> {% if errors %} <div class="alert alert-danger fade in" role="alert"> @@ -93,13 +95,14 @@ </div> <script src="{{ url_for('static', filename='js/jquery-1.11.1.min.js') }}"></script> <script src="{{ url_for('static', filename='js/bootstrap.min.js') }}"></script> - {% if autocomplete %}<script src="{{ url_for('static', filename='js/typeahead.bundle.min.js') }}"></script>{% endif %} + {% if autocomplete %} <script src="{{ url_for('static', filename='js/typeahead.bundle.min.js') }}"></script>{% endif %} + <script src="{{ url_for('static', filename='js/require-2.1.15.min.js') }}"></script> <script src="{{ url_for('static', filename='js/searx.min.js') }}" data-method="{{ method or 'POST' }}" data-autocompleter="{% if autocomplete %}true{% else %}false{% endif %}"></script> {% for script in scripts %} - <script src="{{ url_for('static', filename=script) }}"></script> + {{""}}<script src="{{ url_for('static', filename=script) }}"></script> {% endfor %} <noscript> <style> diff --git a/searx/templates/oscar/categories.html b/searx/templates/oscar/categories.html index 1ace10f16..a5c5f61c7 100644 --- a/searx/templates/oscar/categories.html +++ b/searx/templates/oscar/categories.html @@ -1,13 +1,13 @@ <div id="categories"> -{% if rtl %} - {% for category in categories | reverse %} - <input class="hidden" type="checkbox" id="checkbox_{{ category|replace(' ', '_') }}" name="category_{{ category }}" {% if category in selected_categories %}checked="checked"{% endif %} /> +{%- if rtl -%} + {% for category in categories | reverse -%} + <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> - {% endfor %} -{% else %} - {% for category in categories %} - <input class="hidden" type="checkbox" id="checkbox_{{ category|replace(' ', '_') }}" name="category_{{ category }}" {% if category in selected_categories %}checked="checked"{% endif %} /> + {%- endfor %} +{%- else -%} + {% for category in categories -%} + <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> - {% endfor %} -{% endif %} + {%- endfor %} +{%- endif -%} </div> diff --git a/searx/templates/oscar/infobox.html b/searx/templates/oscar/infobox.html index c98fb0e63..9f5e58d2b 100644 --- a/searx/templates/oscar/infobox.html +++ b/searx/templates/oscar/infobox.html @@ -1,34 +1,35 @@ {% from 'oscar/macros.html' import result_link with context %} <div class="panel panel-default infobox"> - <div class="panel-heading"> - <h4 class="panel-title infobox_part"><bdi>{{ infobox.infobox }}</bdi></h4> + <div class="panel-heading">{{- "" -}} + <h4 class="panel-title infobox_part"><bdi>{{ infobox.infobox }}</bdi></h4>{{- "" -}} </div> <div class="panel-body"> {% if infobox.img_src %}<img class="img-responsive center-block infobox_part" src="{{ image_proxify(infobox.img_src) }}" alt="{{ infobox.infobox }}" />{% endif %} - {% if infobox.content %}<bdi><p class="infobox_part">{{ infobox.content }}</bdi></p>{% endif %} - {% if infobox.attributes %} + {% if infobox.content %}<bdi><p class="infobox_part">{{ infobox.content }}</p></bdi>{% endif %} + + {% if infobox.attributes -%} <table class="table table-striped infobox_part"> - {% for attribute in infobox.attributes %} - <tr> + {% for attribute in infobox.attributes -%} + <tr>{{- "" -}} <td><bdi>{{ attribute.label }}</bdi></td> - {% if attribute.image %} + {%- if attribute.image -%} <td><img class="img-responsive" src="{{ image_proxify(attribute.image.src) }}" alt="{{ attribute.image.alt }}" /></td> - {% else %} + {%- else -%} <td><bdi>{{ attribute.value }}</bdi></td> - {% endif %} + {%- endif -%} </tr> - {% endfor %} + {% endfor -%} </table> {% endif %} - {% if infobox.urls %} - <div class="infobox_part"> + {% if infobox.urls -%} + <div class="infobox_part">{{- "\n" -}} <bdi> - {% for url in infobox.urls %} - <p class="btn btn-default btn-xs">{{ result_link(url.url, url.title) }}</a></p> - {% endfor %} - </bdi> + {%- for url in infobox.urls -%} + <p class="btn btn-default btn-xs">{{ result_link(url.url, url.title) }}</p> + {% endfor -%} + </bdi>{{- "" -}} </div> {% endif %} </div> diff --git a/searx/templates/oscar/languages.html b/searx/templates/oscar/languages.html index 53ade43b2..5aff9f918 100644 --- a/searx/templates/oscar/languages.html +++ b/searx/templates/oscar/languages.html @@ -1,12 +1,8 @@ -{% if preferences %} -<select class="custom-select form-control" name='language'> -{% else %} -<select class="time_range custom-select form-control" id='language' name='language'> -{% endif %} - <option value="all" {% if current_language == 'all' %}selected="selected"{% endif %}>{{ _('Default language') }}</option> - {% for lang_id,lang_name,country_name,english_name in language_codes | sort(attribute=1) %} - <option value="{{ lang_id }}" {% if lang_id == current_language %}selected="selected"{% endif %}> - {{ lang_name }} {% if country_name %}({{ country_name }}) {% endif %}- {{ lang_id }} - </option> - {% endfor %} +<select class="language custom-select form-control" id="language" name="language" accesskey="l"> + <option value="all" {% if current_language == 'all' %}selected="selected"{% endif %}>{{ _('Default language') }}</option> +{%- for lang_id,lang_name,country_name,english_name in language_codes | sort(attribute=1) -%} + <option value="{{ lang_id }}" {% if lang_id == current_language %}selected="selected"{% endif %}> + {{- lang_name }} {% if country_name %}({{ country_name }}) {% endif %}- {{ lang_id -}} + </option> +{%- endfor -%} </select> diff --git a/searx/templates/oscar/macros.html b/searx/templates/oscar/macros.html index 5f6463642..d2d1dc643 100644 --- a/searx/templates/oscar/macros.html +++ b/searx/templates/oscar/macros.html @@ -26,38 +26,38 @@ <!-- Draw result footer --> {% macro result_footer(result) -%} - <div class="clearfix"></div> + <div class="clearfix"></div>{{- "" -}} <div class="pull-right"> - {% for engine in result.engines %} - <span class="label label-default">{{ engine }}</span> - {% endfor %} - {% if result.url %} - <small>{{ result_link("https://web.archive.org/web/" + result.url, icon('link') + _('cached'), "text-info") }}</small> - {% endif %} - {% if proxify %} - <small>{{ result_link(proxify(result.url), icon('sort') + _('proxied'), "text-info") }}</small> - {% endif %} -</div> -{% if result.pretty_url %} -<div class="external-link">{{ result.pretty_url }}</div> -{% endif %} + {%- for engine in result.engines -%} + <span class="label label-default">{{ engine }}</span> + {%- endfor -%} + {%- if result.url -%} + <small>{{ result_link("https://web.archive.org/web/" + result.url, icon('link') + _('cached'), "text-info") }}</small> + {%- endif -%} + {%- if proxify -%} + <small>{{ result_link(proxify(result.url), icon('sort') + _('proxied'), "text-info") }}</small> + {%- endif -%} + </div> + {%- if result.pretty_url -%} + <div class="external-link">{{ result.pretty_url }}</div> + {%- endif -%} {%- endmacro %} <!-- Draw result footer --> {% macro result_footer_rtl(result) -%} - <div class="clearfix"></div> - {% for engine in result.engines %} + <div class="clearfix"></div>{{- "" -}} + {% for engine in result.engines -%} <span class="label label-default">{{ engine }}</span> - {% endfor %} - {% if result.url %} + {%- endfor %} + {%- if result.url -%} <small>{{ result_link("https://web.archive.org/web/" + result.url, icon('link') + _('cached'), "text-info") }}</small> - {% endif %} - {% if proxify %} + {%- endif -%} + {% if proxify -%} <small>{{ result_link(proxify(result.url), icon('sort') + _('proxied'), "text-info") }}</small> - {% endif %} - {% if result.pretty_url %} + {%- endif %} + {%- if result.pretty_url -%} <div class="external-link">{{ result.pretty_url }}</div> - {% endif %} + {%- endif %} {%- endmacro %} {% macro preferences_item_header(info, label, rtl) -%} diff --git a/searx/templates/oscar/navbar.html b/searx/templates/oscar/navbar.html index 12bf14ffa..077fb9f15 100644 --- a/searx/templates/oscar/navbar.html +++ b/searx/templates/oscar/navbar.html @@ -1,9 +1,9 @@ -<div class="searx-navbar"> - <span class="instance {% if rtl %}pull-right{% else %}pull-left{% endif%}"> - <a href="{{ url_for('index') }}">{{ instance_name }}</a> - </span> - <span class="{% if rtl %}pull-left{% else %}pull-right{% endif %}"> - <a href="{{ url_for('about') }}">{{ _('about') }}</a> - <a href="{{ url_for('preferences') }}">{{ _('preferences') }}</a> - </span> +<div class="searx-navbar">{{- "" -}} + <span class="instance {% if rtl %}pull-right{% else %}pull-left{% endif%}">{{- "" -}} + <a href="{{ url_for('index') }}">{{ instance_name }}</a>{{- "" -}} + </span>{{- "" -}} + <span class="{% if rtl %}pull-left{% else %}pull-right{% endif %}">{{- "" -}} + <a href="{{ url_for('about') }}">{{ _('about') }}</a>{{- "" -}} + <a href="{{ url_for('preferences') }}">{{ _('preferences') }}</a>{{- "" -}} + </span>{{- "" -}} </div> diff --git a/searx/templates/oscar/preferences.html b/searx/templates/oscar/preferences.html index b64d72ddf..1a484dd4b 100644 --- a/searx/templates/oscar/preferences.html +++ b/searx/templates/oscar/preferences.html @@ -41,7 +41,7 @@ {% set language_label = _('Search language') %} {% set language_info = _('What language do you prefer for search?') %} {{ preferences_item_header(language_info, language_label, rtl) }} - {% include 'oscar/languages.html' %} + {% include 'oscar/languages.html' %} {{ preferences_item_footer(language_info, language_label, rtl) }} {% set locale_label = _('Interface language') %} @@ -156,26 +156,26 @@ <div class="container-fluid"> <fieldset> <div class="table-responsive"> - <table class="table table-hover table-condensed table-striped"> - <tr> + <table class="table table-hover table-condensed table-striped"> + <tr> {% if not rtl %} - <th>{{ _("Allow") }}</th> - <th>{{ _("Engine name") }}</th> - <th>{{ _("Shortcut") }}</th> - <th>{{ _("Selected language") }}</th> - <th>{{ _("SafeSearch") }}</th> - <th>{{ _("Time range") }}</th> - <th>{{ _("Avg. time") }}</th> - <th>{{ _("Max time") }}</th> + <th>{{ _("Allow") }}</th> + <th>{{ _("Engine name") }}</th> + <th>{{ _("Shortcut") }}</th> + <th>{{ _("Selected language") }}</th> + <th>{{ _("SafeSearch") }}</th> + <th>{{ _("Time range") }}</th> + <th>{{ _("Avg. time") }}</th> + <th>{{ _("Max time") }}</th> {% else %} - <th>{{ _("Max time") }}</th> - <th>{{ _("Avg. time") }}</th> - <th>{{ _("Time range") }}</th> - <th>{{ _("SafeSearch") }}</th> - <th>{{ _("Selected language") }}</th> - <th>{{ _("Shortcut") }}</th> - <th>{{ _("Engine name") }}</th> - <th>{{ _("Allow") }}</th> + <th>{{ _("Max time") }}</th> + <th>{{ _("Avg. time") }}</th> + <th>{{ _("Time range") }}</th> + <th>{{ _("SafeSearch") }}</th> + <th>{{ _("Selected language") }}</th> + <th>{{ _("Shortcut") }}</th> + <th>{{ _("Engine name") }}</th> + <th>{{ _("Allow") }}</th> {% endif %} </tr> {% for search_engine in engines_by_category[categ] %} @@ -186,19 +186,19 @@ {{ checkbox_toggle('engine_' + search_engine.name|replace(' ', '_') + '__' + categ|replace(' ', '_'), (search_engine.name, categ) in disabled_engines) }} </td> <th>{{ search_engine.name }}</th> - <td class="name">{{ shortcuts[search_engine.name] }}</td> - <td>{{ support_toggle(stats[search_engine.name].supports_selected_language) }}</td> - <td>{{ support_toggle(search_engine.safesearch==True) }}</td> - <td>{{ support_toggle(search_engine.time_range_support==True) }}</td> - <td class="{{ 'danger' if stats[search_engine.name]['warn_time'] else '' }}">{{ 'N/A' if stats[search_engine.name].time==None else stats[search_engine.name].time }}</td> - <td class="{{ 'danger' if stats[search_engine.name]['warn_timeout'] else '' }}">{{ search_engine.timeout }}</td> - {% else %} - <td class="{{ 'danger' if stats[search_engine.name]['warn_timeout'] else '' }}">{{ search_engine.timeout }}</td> - <td class="{{ 'danger' if stats[search_engine.name]['warn_time'] else '' }}">{{ 'N/A' if stats[search_engine.name].time==None else stats[search_engine.name].time }}</td> - <td>{{ support_toggle(search_engine.time_range_support==True) }}</td> - <td>{{ support_toggle(search_engine.safesearch==True) }}</td> - <td>{{ support_toggle(stats[search_engine.name].supports_selected_language) }}</td> - <td>{{ shortcuts[search_engine.name] }}</td> + <td class="name">{{ shortcuts[search_engine.name] }}</td> + <td>{{ support_toggle(stats[search_engine.name].supports_selected_language) }}</td> + <td>{{ support_toggle(search_engine.safesearch==True) }}</td> + <td>{{ support_toggle(search_engine.time_range_support==True) }}</td> + <td class="{{ 'danger' if stats[search_engine.name]['warn_time'] else '' }}">{{ 'N/A' if stats[search_engine.name].time==None else stats[search_engine.name].time }}</td> + <td class="{{ 'danger' if stats[search_engine.name]['warn_timeout'] else '' }}">{{ search_engine.timeout }}</td> + {% else %} + <td class="{{ 'danger' if stats[search_engine.name]['warn_timeout'] else '' }}">{{ search_engine.timeout }}</td> + <td class="{{ 'danger' if stats[search_engine.name]['warn_time'] else '' }}">{{ 'N/A' if stats[search_engine.name].time==None else stats[search_engine.name].time }}</td> + <td>{{ support_toggle(search_engine.time_range_support==True) }}</td> + <td>{{ support_toggle(search_engine.safesearch==True) }}</td> + <td>{{ support_toggle(stats[search_engine.name].supports_selected_language) }}</td> + <td>{{ shortcuts[search_engine.name] }}</td> <th>{{ search_engine.name }}</th> <td class="onoff-checkbox"> {{ checkbox_toggle('engine_' + search_engine.name|replace(' ', '_') + '__' + categ|replace(' ', '_'), (search_engine.name, categ) in disabled_engines) }} @@ -207,7 +207,7 @@ </tr> {% endif %} {% endfor %} - </table> + </table> </div> </fieldset> </div> diff --git a/searx/templates/oscar/result_templates/code.html b/searx/templates/oscar/result_templates/code.html index ba74d0333..a1c18a6b7 100644 --- a/searx/templates/oscar/result_templates/code.html +++ b/searx/templates/oscar/result_templates/code.html @@ -1,18 +1,18 @@ -{% from 'oscar/macros.html' import result_header, result_sub_header, result_footer, result_footer_rtl, icon %}
-
-{{ result_header(result, favicons) }}
-{{ result_sub_header(result) }}
-
-{% if result.content %}<p class="result-content">{{ result.content|safe }}</p>{% endif %}
-
-{% if result.repository %}<p class="result-content">{{ icon('file') }} <a href="{{ result.repository }}" {% if results_on_new_tab %}target="_blank" rel="noopener noreferrer"{% else %}rel="noreferrer"{% endif %}>{{ result.repository }}</a></p>{% endif %}
-
-<div dir="ltr">
-{{ result.codelines|code_highlighter(result.code_language)|safe }}
-</div>
-
-{% if rtl %}
-{{ result_footer_rtl(result) }}
-{% else %}
-{{ result_footer(result) }}
-{% endif %}
+{% from 'oscar/macros.html' import result_header, result_sub_header, result_footer, result_footer_rtl, icon %} + +{{ result_header(result, favicons) }} +{{ result_sub_header(result) }} + +{% if result.content %}<p class="result-content">{{ result.content|safe }}</p>{% endif %} + +{% if result.repository %}<p class="result-content">{{ icon('file') }} <a href="{{ result.repository }}" {% if results_on_new_tab %}target="_blank" rel="noopener noreferrer"{% else %}rel="noreferrer"{% endif %}>{{ result.repository }}</a></p>{% endif %} + +<div dir="ltr"> +{{ result.codelines|code_highlighter(result.code_language)|safe }} +</div> + +{% if rtl %} +{{ result_footer_rtl(result) }} +{% else %} +{{ result_footer(result) }} +{% endif %} diff --git a/searx/templates/oscar/result_templates/default.html b/searx/templates/oscar/result_templates/default.html index 3ed0f3122..885cbbfa8 100644 --- a/searx/templates/oscar/result_templates/default.html +++ b/searx/templates/oscar/result_templates/default.html @@ -1,31 +1,31 @@ -{% from 'oscar/macros.html' import result_header, result_sub_header, result_footer, result_footer_rtl, icon with context %}
-
-{{ result_header(result, favicons) }}
-{{ result_sub_header(result) }}
-
-{% if result.embedded %}
- <small> • <a class="text-info btn-collapse collapsed cursor-pointer media-loader disabled_if_nojs" data-toggle="collapse" data-target="#result-media-{{ index }}" data-btn-text-collapsed="{{ _('show media') }}" data-btn-text-not-collapsed="{{ _('hide media') }}">{{ icon('music') }} {{ _('show media') }}</a></small>
-{% endif %}
-
-{% if result.embedded %}
-<div id="result-media-{{ index }}" class="collapse">
- {{ result.embedded|safe }}
-</div>
-{% endif %}
-
-{% if result.img_src %}
-<div class="container-fluid">
- <div class="row">
-<img src="{{ image_proxify(result.img_src) }}" alt="{{ result.title|striptags }}" title="{{ result.title|striptags }}" style="width: auto; max-height: 60px; min-height: 60px;" class="col-xs-2 col-sm-4 col-md-4 result-content">
-{% if result.content %}<p class="result-content col-xs-8 col-sm-8 col-md-8">{{ result.content|safe }}</p>{% endif %}
- </div>
-</div>
-{% else %}
-{% if result.content %}<p class="result-content">{{ result.content|safe }}</p>{% endif %}
-{% endif %}
-
-{% if rtl %}
-{{ result_footer_rtl(result) }}
-{% else %}
-{{ result_footer(result) }}
-{% endif %}
+{% from 'oscar/macros.html' import result_header, result_sub_header, result_footer, result_footer_rtl, icon with context %} + +{{- result_header(result, favicons) -}} +{{- result_sub_header(result) -}} + +{%- if result.embedded -%} + <small> • <a class="text-info btn-collapse collapsed cursor-pointer media-loader disabled_if_nojs" data-toggle="collapse" data-target="#result-media-{{ index }}" data-btn-text-collapsed="{{ _('show media') }}" data-btn-text-not-collapsed="{{ _('hide media') }}">{{ icon('music') }} {{ _('show media') }}</a></small> +{%- endif -%} + +{%- if result.embedded -%} +<div id="result-media-{{ index }}" class="collapse"> + {{ result.embedded|safe }} +</div> +{%- endif -%} + +{%- if result.img_src -%} +<div class="container-fluid"> + <div class="row"> +<img src="{{ image_proxify(result.img_src) }}" alt="{{ result.title|striptags }}" title="{{ result.title|striptags }}" style="width: auto; max-height: 60px; min-height: 60px;" class="col-xs-2 col-sm-4 col-md-4 result-content"> +{% if result.content %}<p class="result-content col-xs-8 col-sm-8 col-md-8">{{ result.content|safe }}</p>{% endif -%} + </div> +</div> +{%- else -%} +{%- if result.content %}<p class="result-content">{{ result.content|safe }}</p>{% endif -%} +{%- endif -%} + +{%- if rtl -%} +{{ result_footer_rtl(result) }} +{%- else -%} +{{ result_footer(result) }} +{%- endif -%} diff --git a/searx/templates/oscar/result_templates/images.html b/searx/templates/oscar/result_templates/images.html index b3292f448..d0a3b7b83 100644 --- a/searx/templates/oscar/result_templates/images.html +++ b/searx/templates/oscar/result_templates/images.html @@ -1,49 +1,36 @@ -{% from 'oscar/macros.html' import draw_favicon %}
-
-<a href="{{ result.img_src }}" {% if results_on_new_tab %}target="_blank" rel="noopener noreferrer"{% else %}rel="noreferrer"{% endif %} data-toggle="modal" data-target="#modal-{{ index }}-{{pageno}}">
- <img src="{% if result.thumbnail_src %}{{ image_proxify(result.thumbnail_src) }}{% else %}{{ image_proxify(result.img_src) }}{% endif %}" alt="{{ result.title|striptags }}" title="{{ result.title|striptags }}" class="img-thumbnail">
-</a>
-
-<div class="modal fade" id="modal-{{ index }}-{{ pageno }}" tabindex="-1" role="dialog" aria-hidden="true">
- <div class="modal-dialog">
- <div class="modal-wrapper">
- <div class="modal-header">
- <button type="button" class="close" data-dismiss="modal"><span aria-hidden="true">×</span><span class="sr-only">Close</span></button>
- <h4 class="modal-title">{% if result.engine~".png" in favicons %}{{ draw_favicon(result.engine) }} {% endif %}{{ result.title|striptags }}</h4>
- </div>
- <div class="modal-body">
- <img class="img-responsive center-block" src="{% if result.thumbnail_src %}{{ image_proxify(result.thumbnail_src) }}{% else %}{{ image_proxify(result.img_src) }}{% endif %}" alt="{{ result.title|striptags }}">
- {% if result.author %}<span class="photo-author">{{ result.author }}</span><br />{% endif %}
- {% if result.content %}
- <p class="result-content">
- {{ result.content|striptags }}
- </p>
- {% endif %}
- {% if result.img_format %}
- <p class="result-format">
- {{ result.img_format }}
- </p>
- {% endif %}
- {% if result.source %}
- <p class="result-source">
- {{ result.source }}
- </p>
- {% endif %}
- </div>
- <div class="modal-footer">
- <div class="clearfix"></div>
- <span class="label label-default pull-right">{{ result.engine }}</span>
- <p class="text-muted pull-left">{{ result.pretty_url }}</p>
- <div class="clearfix"></div>
- <div class="row">
- <div class="col-md-6">
- <a href="{{ result.img_src }}" {% if results_on_new_tab %}target="_blank" rel="noopener noreferrer"{% else %}rel="noreferrer"{% endif %} class="btn btn-default">{{ _('Get image') }}</a>
- </div>
- <div class="col-md-6">
- <a href="{{ result.url }}" {% if results_on_new_tab %}target="_blank" rel="noopener noreferrer"{% else %}rel="noreferrer"{% endif %} class="btn btn-default">{{ _('View source') }}</a>
- </div>
- </div>
- </div>
- </div>
- </div>
-</div>
+{%- from 'oscar/macros.html' import draw_favicon -%} + +<a href="{{ result.img_src }}" {% if results_on_new_tab %}target="_blank" rel="noopener noreferrer"{% else %}rel="noreferrer"{% endif %} data-toggle="modal" data-target="#modal-{{ index }}-{{pageno}}">{{- "" -}} + <img src="{% if result.thumbnail_src %}{{ image_proxify(result.thumbnail_src) }}{% else %}{{ image_proxify(result.img_src) }}{% endif %}" alt="{{ result.title|striptags }}" title="{{ result.title|striptags }}" class="img-thumbnail">{{- "" -}} +</a> +<div class="modal fade" id="modal-{{ index }}-{{ pageno }}" tabindex="-1" role="dialog" aria-hidden="true">{{- "" -}} + <div class="modal-dialog">{{- "" -}} + <div class="modal-wrapper">{{- "" -}} + <div class="modal-header">{{- "" -}} + <button type="button" class="close" data-dismiss="modal"><span aria-hidden="true">×</span><span class="sr-only">Close</span></button>{{- "" -}} + <h4 class="modal-title">{% if result.engine~".png" in favicons %}{{ draw_favicon(result.engine) }} {% endif %}{{ result.title|striptags }}</h4>{{- "" -}} + </div>{{- "" -}} + <div class="modal-body">{{- "" -}} + <img class="img-responsive center-block" src="{% if result.thumbnail_src %}{{ image_proxify(result.thumbnail_src) }}{% else %}{{ image_proxify(result.img_src) }}{% endif %}" alt="{{ result.title|striptags }}"> + {%- if result.author %}<span class="photo-author">{{ result.author }}</span><br />{% endif -%} + {%- if result.content %}<p class="result-content">{{ result.content|striptags }}</p>{% endif -%} + {%- if result.img_format %}<p class="result-format">{{ result.img_format }}</p>{% endif -%} + {%- if result.source %}<p class="result-source">{{ result.source }}</p>{% endif -%} + </div>{{- "" -}} + <div class="modal-footer">{{- "" -}} + <div class="clearfix"></div>{{- "" -}} + <span class="label label-default pull-right">{{ result.engine }}</span>{{- "" -}} + <p class="text-muted pull-left">{{ result.pretty_url }}</p>{{- "" -}} + <div class="clearfix"></div>{{- "" -}} + <div class="row">{{- "" -}} + <div class="col-md-6">{{- "" -}} + <a href="{{ result.img_src }}" {% if results_on_new_tab %}target="_blank" rel="noopener noreferrer"{% else %}rel="noreferrer"{% endif %} class="btn btn-default">{{ _('Get image') }}</a>{{- "" -}} + </div>{{- "" -}} + <div class="col-md-6">{{- "" -}} + <a href="{{ result.url }}" {% if results_on_new_tab %}target="_blank" rel="noopener noreferrer"{% else %}rel="noreferrer"{% endif %} class="btn btn-default">{{ _('View source') }}</a>{{- "" -}} + </div>{{- "" -}} + </div>{{- "" -}} + </div>{{- "" -}} + </div>{{- "" -}} + </div>{{- "" -}} +</div>{{- "" -}} diff --git a/searx/templates/oscar/result_templates/map.html b/searx/templates/oscar/result_templates/map.html index 822c7cdea..712375d7f 100644 --- a/searx/templates/oscar/result_templates/map.html +++ b/searx/templates/oscar/result_templates/map.html @@ -1,72 +1,72 @@ -{% from 'oscar/macros.html' import result_header, result_sub_header, result_footer, result_footer_rtl, icon %}
-
-{{ result_header(result, favicons) }}
-{{ result_sub_header(result) }}
-
-{% if (result.latitude and result.longitude) or result.boundingbox %}
- <small> • <a class="text-info btn-collapse collapsed searx_init_map cursor-pointer disabled_if_nojs" data-toggle="collapse" data-target="#result-map-{{ index }}" data-leaflet-target="osm-map-{{ index }}" data-map-lon="{{ result.longitude }}" data-map-lat="{{ result.latitude }}" {% if result.boundingbox %}data-map-boundingbox='{{ result.boundingbox|tojson|safe }}'{% endif %} {% if result.geojson %}data-map-geojson='{{ result.geojson|tojson|safe }}'{% endif %} data-btn-text-collapsed="{{ _('show map') }}" data-btn-text-not-collapsed="{{ _('hide map') }}">{{ icon('globe') }} {{ _('show map') }}</a></small>
-{% endif %}
-
-{% if result.osm and (result.osm.type and result.osm.id) %}
- <small> • <a class="text-info btn-collapse collapsed cursor-pointer searx_overpass_request disabled_if_nojs" data-toggle="collapse" data-target="#result-overpass-{{ index }}" data-osm-type="{{ result.osm.type }}" data-osm-id="{{ result.osm.id }}" data-result-table="result-overpass-table-{{ index }}" data-result-table-loadicon="result-overpass-table-loading-{{ index }}" data-btn-text-collapsed="{{ _('show details') }}" data-btn-text-not-collapsed="{{ _('hide details') }}">{{ icon('map-marker') }} {{ _('show details') }}</a></small>
-{% endif %}
-
-{# {% if (result.latitude and result.longitude) %}
- <small> • <a class="text-info btn-collapse collapsed cursor-pointer disabled_if_nojs" data-toggle="collapse" data-target="#result-geodata-{{ index }}" data-btn-text-collapsed="{{ _('show geodata') }}" data-btn-text-not-collapsed="{{ _('hide geodata') }}">{{ icon('map-marker') }} {{ _('show geodata') }}</a></small>
-{% endif %} #}
-
-<div class="container-fluid">
-
-{% if result.address %}
-<p class="row result-content result-adress col-xs-12 col-sm-5 col-md-4" itemscope itemtype="http://schema.org/PostalAddress">
- {% if result.address.name %}
- <strong itemprop="name">{{ result.address.name }}</strong><br/>
- {% endif %}
- {% if result.address.road %}
- <span itemprop="streetAddress">
- {% if result.address.house_number %}{{ result.address.house_number }}, {% endif %}
- {{ result.address.road }}
- </span><br/>
- {% endif %}
- {% if result.address.locality %}
- <span itemprop="addressLocality">{{ result.address.locality }}</span>
- {% if result.address.postcode %}, <span itemprop="postalCode">{{ result.address.postcode }}</span>{% endif %}
- <br/>
- {% endif %}
- {% if result.address.country %}
- <span itemprop="addressCountry">{{ result.address.country }}</span>
- {% endif %}
-</p>
-{% endif %}
-
-{% if result.osm and (result.osm.type and result.osm.id) %}
- <div class="row result-content collapse col-xs-12 col-sm-7 col-md-8" id="result-overpass-{{ index }}"{% if rtl %} dir="ltr"{% endif %}>
- <div class="text-center" id="result-overpass-table-loading-{{ index }}"><img src="{{ url_for('static', filename='img/loader.gif') }}" alt="Loading ..."/></div>
- <table class="table table-striped table-condensed hidden" id="result-overpass-table-{{ index }}">
- <tr><th>key</th><th>value</th></tr>
- </table>
- </div>
-{% endif %}
-
-{# {% if (result.latitude and result.longitude) %}
- <div class="row collapse col-xs-12 col-sm-5 col-md-4" id="result-geodata-{{ index }}">
- <strong>Longitude:</strong> {{ result.longitude }} <br/>
- <strong>Latitude:</strong> {{ result.latitude }}
- </div>
-{% endif %} #}
-
-{% if result.content %}<p class="row result-content col-xs-12 col-sm-12 col-md-12">{{ result.content|safe }}</p>{% endif %}
-
-</div>
-
-{% if (result.latitude and result.longitude) or result.boundingbox %}
- <div class="collapse" id="result-map-{{ index }}">
- <div style="height:300px; width:100%; margin: 10px 0;" id="osm-map-{{ index }}"></div>
- </div>
-{% endif %}
-
-{% if rtl %}
-{{ result_footer_rtl(result) }}
-{% else %}
-{{ result_footer(result) }}
-{% endif %}
+{% from 'oscar/macros.html' import result_header, result_sub_header, result_footer, result_footer_rtl, icon %} + +{{ result_header(result, favicons) }} +{{ result_sub_header(result) }} + +{% if (result.latitude and result.longitude) or result.boundingbox %} + <small> • <a class="text-info btn-collapse collapsed searx_init_map cursor-pointer disabled_if_nojs" data-toggle="collapse" data-target="#result-map-{{ index }}" data-leaflet-target="osm-map-{{ index }}" data-map-lon="{{ result.longitude }}" data-map-lat="{{ result.latitude }}" {% if result.boundingbox %}data-map-boundingbox='{{ result.boundingbox|tojson|safe }}'{% endif %} {% if result.geojson %}data-map-geojson='{{ result.geojson|tojson|safe }}'{% endif %} data-btn-text-collapsed="{{ _('show map') }}" data-btn-text-not-collapsed="{{ _('hide map') }}">{{ icon('globe') }} {{ _('show map') }}</a></small> +{% endif %} + +{% if result.osm and (result.osm.type and result.osm.id) %} + <small> • <a class="text-info btn-collapse collapsed cursor-pointer searx_overpass_request disabled_if_nojs" data-toggle="collapse" data-target="#result-overpass-{{ index }}" data-osm-type="{{ result.osm.type }}" data-osm-id="{{ result.osm.id }}" data-result-table="result-overpass-table-{{ index }}" data-result-table-loadicon="result-overpass-table-loading-{{ index }}" data-btn-text-collapsed="{{ _('show details') }}" data-btn-text-not-collapsed="{{ _('hide details') }}">{{ icon('map-marker') }} {{ _('show details') }}</a></small> +{% endif %} + +{# {% if (result.latitude and result.longitude) %} + <small> • <a class="text-info btn-collapse collapsed cursor-pointer disabled_if_nojs" data-toggle="collapse" data-target="#result-geodata-{{ index }}" data-btn-text-collapsed="{{ _('show geodata') }}" data-btn-text-not-collapsed="{{ _('hide geodata') }}">{{ icon('map-marker') }} {{ _('show geodata') }}</a></small> +{% endif %} #} + +<div class="container-fluid"> + +{% if result.address %} +<p class="row result-content result-adress col-xs-12 col-sm-5 col-md-4" itemscope itemtype="http://schema.org/PostalAddress"> + {% if result.address.name %} + <strong itemprop="name">{{ result.address.name }}</strong><br/> + {% endif %} + {% if result.address.road %} + <span itemprop="streetAddress"> + {% if result.address.house_number %}{{ result.address.house_number }}, {% endif %} + {{ result.address.road }} + </span><br/> + {% endif %} + {% if result.address.locality %} + <span itemprop="addressLocality">{{ result.address.locality }}</span> + {% if result.address.postcode %}, <span itemprop="postalCode">{{ result.address.postcode }}</span>{% endif %} + <br/> + {% endif %} + {% if result.address.country %} + <span itemprop="addressCountry">{{ result.address.country }}</span> + {% endif %} +</p> +{% endif %} + +{% if result.osm and (result.osm.type and result.osm.id) %} + <div class="row result-content collapse col-xs-12 col-sm-7 col-md-8" id="result-overpass-{{ index }}"{% if rtl %} dir="ltr"{% endif %}> + <div class="text-center" id="result-overpass-table-loading-{{ index }}"><img src="{{ url_for('static', filename='img/loader.gif') }}" alt="Loading ..."/></div> + <table class="table table-striped table-condensed hidden" id="result-overpass-table-{{ index }}"> + <tr><th>key</th><th>value</th></tr> + </table> + </div> +{% endif %} + +{# {% if (result.latitude and result.longitude) %} + <div class="row collapse col-xs-12 col-sm-5 col-md-4" id="result-geodata-{{ index }}"> + <strong>Longitude:</strong> {{ result.longitude }} <br/> + <strong>Latitude:</strong> {{ result.latitude }} + </div> +{% endif %} #} + +{% if result.content %}<p class="row result-content col-xs-12 col-sm-12 col-md-12">{{ result.content|safe }}</p>{% endif %} + +</div> + +{% if (result.latitude and result.longitude) or result.boundingbox %} + <div class="collapse" id="result-map-{{ index }}"> + <div style="height:300px; width:100%; margin: 10px 0;" id="osm-map-{{ index }}"></div> + </div> +{% endif %} + +{% if rtl %} +{{ result_footer_rtl(result) }} +{% else %} +{{ result_footer(result) }} +{% endif %} diff --git a/searx/templates/oscar/result_templates/torrent.html b/searx/templates/oscar/result_templates/torrent.html index f5ea415e2..089367e36 100644 --- a/searx/templates/oscar/result_templates/torrent.html +++ b/searx/templates/oscar/result_templates/torrent.html @@ -3,7 +3,7 @@ {{ result_header(result, favicons) }} {{ result_sub_header(result) }} -{% if result.seed %}<p class="result-content">{{ icon('transfer') }} {{ _('Seeder') }} <span class="badge">{{ result.seed }}</span> • {{ _('Leecher') }} <span class="badge">{{ result.leech }}</span>{% endif %} +{% if result.seed is defined %}<p class="result-content">{{ icon('transfer') }} {{ _('Seeder') }} <span class="badge">{{ result.seed }}</span> • {{ _('Leecher') }} <span class="badge">{{ result.leech }}</span>{% endif %} {% if result.filesize %}<br />{{ icon('floppy-disk') }} {{ _('Filesize') }} <span class="badge"> {% if result.filesize < 1024 %}{{ result.filesize }} {{ _('Bytes') }} diff --git a/searx/templates/oscar/result_templates/videos.html b/searx/templates/oscar/result_templates/videos.html index 36fb26240..3c1913d9d 100644 --- a/searx/templates/oscar/result_templates/videos.html +++ b/searx/templates/oscar/result_templates/videos.html @@ -1,27 +1,27 @@ -{% from 'oscar/macros.html' import result_header, result_sub_header, result_footer, result_footer_rtl, icon %}
-
-{{ result_header(result, favicons) }}
-{{ result_sub_header(result) }}
-
-{% if result.embedded %}
- <small> • <a class="text-info btn-collapse collapsed cursor-pointer media-loader disabled_if_nojs" data-toggle="collapse" data-target="#result-video-{{ index }}" data-btn-text-collapsed="{{ _('show video') }}" data-btn-text-not-collapsed="{{ _('hide video') }}">{{ icon('film') }} {{ _('show video') }}</a></small>
-{% endif %}
-
-{% if result.embedded %}
-<div id="result-video-{{ index }}" class="collapse">
- {{ result.embedded|safe }}
-</div>
-{% endif %}
-
-<div class="container-fluid">
- <div class="row">
- <a href="{{ result.url }}" {% if results_on_new_tab %}target="_blank" rel="noopener noreferrer"{% else %}rel="noreferrer"{% endif %}><img class="thumbnail col-xs-6 col-sm-4 col-md-4 result-content" src="{{ image_proxify(result.thumbnail) }}" alt="{{ result.title|striptags }} {{ result.engine }}" /></a>
- {% if result.content %}<p class="col-xs-12 col-sm-8 col-md-8 result-content">{{ result.content|safe }}</p>{% endif %}
- </div>
-</div>
-
-{% if rtl %}
-{{ result_footer_rtl(result) }}
-{% else %}
-{{ result_footer(result) }}
-{% endif %}
+{% from 'oscar/macros.html' import result_header, result_sub_header, result_footer, result_footer_rtl, icon %} + +{{ result_header(result, favicons) }} +{{ result_sub_header(result) }} + +{% if result.embedded %} + <small> • <a class="text-info btn-collapse collapsed cursor-pointer media-loader disabled_if_nojs" data-toggle="collapse" data-target="#result-video-{{ index }}" data-btn-text-collapsed="{{ _('show video') }}" data-btn-text-not-collapsed="{{ _('hide video') }}">{{ icon('film') }} {{ _('show video') }}</a></small> +{% endif %} + +{% if result.embedded %} +<div id="result-video-{{ index }}" class="collapse"> + {{ result.embedded|safe }} +</div> +{% endif %} + +<div class="container-fluid"> + <div class="row"> + <a href="{{ result.url }}" {% if results_on_new_tab %}target="_blank" rel="noopener noreferrer"{% else %}rel="noreferrer"{% endif %}><img class="thumbnail col-xs-6 col-sm-4 col-md-4 result-content" src="{{ image_proxify(result.thumbnail) }}" alt="{{ result.title|striptags }} {{ result.engine }}" /></a> + {% if result.content %}<p class="col-xs-12 col-sm-8 col-md-8 result-content">{{ result.content|safe }}</p>{% endif %} + </div> +</div> + +{% if rtl %} +{{ result_footer_rtl(result) }} +{% else %} +{{ result_footer(result) }} +{% endif %} diff --git a/searx/templates/oscar/results.html b/searx/templates/oscar/results.html index fce7f97d6..9cf942695 100644 --- a/searx/templates/oscar/results.html +++ b/searx/templates/oscar/results.html @@ -1,156 +1,156 @@ -{% extends "oscar/base.html" %}
-{% macro search_form_attrs(pageno) -%}
- {% for category in selected_categories %}<input type="hidden" name="category_{{ category }}" value="1"/>{% endfor %}
- <input type="hidden" name="q" value="{{ q|e }}" />
- <input type="hidden" name="pageno" value="{{ pageno }}" />
- <input type="hidden" name="time_range" value="{{ time_range }}" />
- <input type="hidden" name="language" value="{{ current_language }}" />
- {% if timeout_limit %}<input type="hidden" name="timeout_limit" value="{{ timeout_limit|e }}" />{% endif %}
-{%- endmacro %}
-{%- macro search_url() %}{{ base_url }}?q={{ q|urlencode }}{% if selected_categories %}&categories={{ selected_categories|join(",") | replace(' ','+') }}{% endif %}{% if pageno > 1 %}&pageno={{ pageno }}{% endif %}{% if time_range %}&time_range={{ time_range }}{% endif %}{% if current_language != 'all' %}&language={{ current_language }}{% endif %}{% endmacro -%}
-
-{% block title %}{{ q|e }} - {% endblock %}
-{% block meta %}<link rel="alternate" type="application/rss+xml" title="Searx search: {{ q|e }}" href="{{ search_url() }}&format=rss">{% endblock %}
-{% block content %}
- {% include 'oscar/search.html' %}
- <div class="row">
- <div class="col-sm-8" id="main_results">
- <h1 class="sr-only">{{ _('Search results') }}</h1>
-
- {% if corrections %}
- <div class="result">
- <span class="result_header text-muted form-inline pull-left suggestion_item">{{ _('Try searching for:') }}</span>
- {% for correction in corrections %}
- <form method="{{ method or 'POST' }}" action="{{ url_for('index') }}" role="navigation" class="form-inline pull-left suggestion_item">
- <input type="hidden" name="q" value="{{ correction.url }}">
- <button type="submit" class="btn btn-default btn-xs">{{ correction.title }}</button>
- </form>
- {% endfor %}
- </div>
- {% endif %}
-
- {% if answers %}
- {% for answer in answers %}
- <div class="result well">
- <span>{{ answer }}</span>
- </div>
- {% endfor %}
- {% endif %}
-
- {% for result in results %}
- <div class="result {% if result['template'] %}result-{{ result.template|replace('.html', '') }}{% else %}result-default{% endif %}">
- {% set index = loop.index %}
- {% if result.template %}
- {% include get_result_template('oscar', result['template']) %}
- {% else %}
- {% include 'oscar/result_templates/default.html' %}
- {% endif %}
- </div>
- {% endfor %}
-
- {% if not results and not answers %}
- {% include 'oscar/messages/no_results.html' %}
- {% endif %}
-
- <div class="clearfix"></div>
-
- {% if paging %}
- {% if rtl %}
- <div id="pagination">
- <div class="pull-left">
- <form method="{{ method or 'POST' }}" action="{{ url_for('index') }}" class="pull-left">
- {{ search_form_attrs(pageno+1) }}
- <button type="submit" class="btn btn-default"><span class="glyphicon glyphicon-backward"></span> {{ _('next page') }}</button>
- </form>
- </div>
- <div class="pull-right">
- <form method="{{ method or 'POST' }}" action="{{ url_for('index') }}" class="pull-left">
- {{ search_form_attrs(pageno-1) }}
- <button type="submit" class="btn btn-default" {% if pageno == 1 %}disabled{% endif %}><span class="glyphicon glyphicon-forward"></span> {{ _('previous page') }}</button>
- </form>
- </div>
- </div><!-- /#pagination -->
- <div class="clearfix"></div>
- {% else %}
- <div id="pagination">
- <div class="pull-left">
- <form method="{{ method or 'POST' }}" action="{{ url_for('index') }}" class="pull-left">
- {{ search_form_attrs(pageno-1) }}
- <button type="submit" class="btn btn-default" {% if pageno == 1 %}disabled{% endif %}><span class="glyphicon glyphicon-backward"></span> {{ _('previous page') }}</button>
- </form>
- </div>
- <div class="pull-right">
- <form method="{{ method or 'POST' }}" action="{{ url_for('index') }}" class="pull-left">
- {{ search_form_attrs(pageno+1) }}
- <button type="submit" class="btn btn-default"><span class="glyphicon glyphicon-forward"></span> {{ _('next page') }}</button>
- </form>
- </div>
- </div><!-- /#pagination -->
- <div class="clearfix"></div>
- {% endif %}
- {% endif %}
- </div><!-- /#main_results -->
-
- <div class="col-sm-4" id="sidebar_results">
- {% if number_of_results != '0' %}
- <p><small>{{ _('Number of results') }}: {{ number_of_results }}</small></p>
- {% endif %}
-
- {% if unresponsive_engines and results|length >= 1 %}
- <div class="alert alert-danger fade in" role="alert">
- <p>{{ _('Engines cannot retrieve results') }}:</p>
- {% for engine_name, error_type in unresponsive_engines %}
- {{ engine_name }} ({{ error_type }}){% if not loop.last %}, {% endif %}
- {% endfor %}
- </div>
- {% endif %}
-
- {% if infoboxes %}
- {% for infobox in infoboxes %}
- {% include 'oscar/infobox.html' %}
- {% endfor %}
- {% endif %}
-
- {% if suggestions %}
- <div class="panel panel-default">
- <div class="panel-heading">
- <h4 class="panel-title">{{ _('Suggestions') }}</h4>
- </div>
- <div class="panel-body">
- {% for suggestion in suggestions %}
- <form method="{{ method or 'POST' }}" action="{{ url_for('index') }}" role="navigation" class="form-inline pull-{% if rtl %}right{% else %}left{% endif %} suggestion_item">
- <input type="hidden" name="q" value="{{ suggestion.url }}">
- <button type="submit" class="btn btn-default btn-xs">{{ suggestion.title }}</button>
- </form>
- {% endfor %}
- </div>
- </div>
- {% endif %}
-
- <div class="panel panel-default">
- <div class="panel-heading">
- <h4 class="panel-title">{{ _('Links') }}</h4>
- </div>
- <div class="panel-body">
- <form role="form">
- <div class="form-group">
- <label for="search_url">{{ _('Search URL') }}</label>
- <input id="search_url" type="url" class="form-control select-all-on-click cursor-text" name="search_url" value="{{ search_url() }}" readonly>
- </div>
- </form>
-
- <label>{{ _('Download results') }}</label>
- <div class="clearfix"></div>
- {% for output_type in ('csv', 'json', 'rss') %}
- <form method="{{ method or 'POST' }}" action="{{ url_for('index') }}" class="form-inline pull-{% if rtl %}right{% else %}left{% endif %} result_download">
- {{ search_form_attrs(pageno) }}
- <input type="hidden" name="format" value="{{ output_type }}">
- <button type="submit" class="btn btn-default">{{ output_type }}</button>
- </form>
- {% endfor %}
- <div class="clearfix"></div>
- </div>
- </div>
- </div><!-- /#sidebar_results -->
- </div>
-{% endblock %}
+{% extends "oscar/base.html" %} +{% macro search_form_attrs(pageno) -%} + {%- for category in selected_categories -%}<input type="hidden" name="category_{{ category }}" value="1"/>{%- endfor -%} + <input type="hidden" name="q" value="{{ q|e }}" />{{- "" -}} + <input type="hidden" name="pageno" value="{{ pageno }}" />{{- "" -}} + <input type="hidden" name="time_range" value="{{ time_range }}" />{{- "" -}} + <input type="hidden" name="language" value="{{ current_language }}" />{{- "" -}} + {% if timeout_limit %}<input type="hidden" name="timeout_limit" value="{{ timeout_limit|e }}" />{% endif -%} +{%- endmacro %} +{%- macro search_url() %}{{ base_url }}?q={{ q|urlencode }}{% if selected_categories %}&categories={{ selected_categories|join(",") | replace(' ','+') }}{% endif %}{% if pageno > 1 %}&pageno={{ pageno }}{% endif %}{% if time_range %}&time_range={{ time_range }}{% endif %}{% if current_language != 'all' %}&language={{ current_language }}{% endif %}{% endmacro -%} + +{% block title %}{{ q|e }} - {% endblock %} +{% block meta %}{{" "}}<link rel="alternate" type="application/rss+xml" title="Searx search: {{ q|e }}" href="{{ search_url() }}&format=rss">{% endblock %} +{% block content %} + {% include 'oscar/search.html' %} + + <div class="row"> + <div class="col-sm-8" id="main_results"> + <h1 class="sr-only">{{ _('Search results') }}</h1> + + {% if corrections -%} + <div class="result"> + <span class="result_header text-muted form-inline pull-left suggestion_item">{{ _('Try searching for:') }}</span> + {% for correction in corrections -%} + <form method="{{ method or 'POST' }}" action="{{ url_for('index') }}" role="navigation" class="form-inline pull-left suggestion_item">{{- "" -}} + <input type="hidden" name="q" value="{{ correction.url }}">{{- "" -}} + <button type="submit" class="btn btn-default btn-xs">{{ correction.title }}</button>{{- "" -}} + </form> + {% endfor %} + </div> + {%- endif %} + + {% if answers -%} + {%- for answer in answers %} + <div class="result well"> + <span>{{ answer }}</span> + </div> + {%- endfor %} + {%- endif %} + + {% for result in results -%} + <div class="result {% if result['template'] %}result-{{ result.template|replace('.html', '') }}{% else %}result-default{% endif %}"> + {%- set index = loop.index -%} + {%- if result.template -%} + {% include get_result_template('oscar', result['template']) %} + {%- else -%} + {% include 'oscar/result_templates/default.html' %} + {%- endif -%} + </div> + {% endfor %} + + {% if not results and not answers -%} + {% include 'oscar/messages/no_results.html' %} + {% endif %} + + <div class="clearfix"></div> + + {% if paging -%} + {% if rtl %} + <div id="pagination"> + <div class="pull-left">{{- "" -}} + <form method="{{ method or 'POST' }}" action="{{ url_for('index') }}" class="pull-left"> + {{- search_form_attrs(pageno+1) -}} + <button type="submit" class="btn btn-default"><span class="glyphicon glyphicon-backward"></span> {{ _('next page') }}</button>{{- "" -}} + </form>{{- "" -}} + </div> + <div class="pull-right">{{- "" -}} + <form method="{{ method or 'POST' }}" action="{{ url_for('index') }}" class="pull-left"> + {{- search_form_attrs(pageno-1) -}} + <button type="submit" class="btn btn-default" {% if pageno == 1 %}disabled{% endif %}><span class="glyphicon glyphicon-forward"></span> {{ _('previous page') }}</button>{{- "" -}} + </form>{{- "" -}} + </div> + </div><!-- /#pagination --> + <div class="clearfix"></div> + {% else %} + <div id="pagination"> + <div class="pull-left">{{- "" -}} + <form method="{{ method or 'POST' }}" action="{{ url_for('index') }}" class="pull-left"> + {{- search_form_attrs(pageno-1) -}} + <button type="submit" class="btn btn-default" {% if pageno == 1 %}disabled{% endif %}><span class="glyphicon glyphicon-backward"></span> {{ _('previous page') }}</button>{{- "" -}} + </form>{{- "" -}} + </div> + <div class="pull-right">{{- "" -}} + <form method="{{ method or 'POST' }}" action="{{ url_for('index') }}" class="pull-left"> + {{- search_form_attrs(pageno+1) -}} + <button type="submit" class="btn btn-default"><span class="glyphicon glyphicon-forward"></span> {{ _('next page') }}</button>{{- "" -}} + </form>{{- "" -}} + </div> + </div><!-- /#pagination --> + <div class="clearfix"></div> + {% endif %} + {% endif %} + </div><!-- /#main_results --> + + <div class="col-sm-4" id="sidebar_results"> + {% if number_of_results != '0' -%} + <p><small>{{ _('Number of results') }}: {{ number_of_results }}</small></p> + {%- endif %} + + {% if unresponsive_engines and results|length >= 1 -%} + <div class="alert alert-danger fade in" role="alert"> + <p>{{ _('Engines cannot retrieve results') }}:</p> + {%- for engine_name, error_type in unresponsive_engines -%} + {{- engine_name }} ({{ error_type }}){% if not loop.last %}, {% endif %}{{- "" -}} + {%- endfor -%} + </div> + {%- endif %} + + {% if infoboxes -%} + {% for infobox in infoboxes %} + {% include 'oscar/infobox.html' %}{{- "\n\n" -}} + {% endfor %} + {%- endif %} + + {% if suggestions %} + <div class="panel panel-default"> + <div class="panel-heading"> + <h4 class="panel-title">{{ _('Suggestions') }}</h4> + </div> + <div class="panel-body"> + {% for suggestion in suggestions %} + <form method="{{ method or 'POST' }}" action="{{ url_for('index') }}" role="navigation" class="form-inline pull-{% if rtl %}right{% else %}left{% endif %} suggestion_item"> + <input type="hidden" name="q" value="{{ suggestion.url }}"> + <button type="submit" class="btn btn-default btn-xs">{{ suggestion.title }}</button> + </form> + {% endfor %} + </div> + </div> + {%- endif %} + + <div class="panel panel-default"> + <div class="panel-heading">{{- "" -}} + <h4 class="panel-title">{{ _('Links') }}</h4>{{- "" -}} + </div> + <div class="panel-body"> + <form role="form">{{- "" -}} + <div class="form-group">{{- "" -}} + <label for="search_url">{{ _('Search URL') }}</label>{{- "" -}} + <input id="search_url" type="url" class="form-control select-all-on-click cursor-text" name="search_url" value="{{ search_url() }}" readonly>{{- "" -}} + </div>{{- "" -}} + </form> + <label>{{ _('Download results') }}</label> + <div class="clearfix"></div> + {% for output_type in ('csv', 'json', 'rss') %} + <form method="{{ method or 'POST' }}" action="{{ url_for('index') }}" class="form-inline pull-{% if rtl %}right{% else %}left{% endif %} result_download"> + {{- search_form_attrs(pageno) -}} + <input type="hidden" name="format" value="{{ output_type }}">{{- "" -}} + <button type="submit" class="btn btn-default">{{ output_type }}</button>{{- "" -}} + </form> + {% endfor %} + <div class="clearfix"></div> + </div> + </div> + </div><!-- /#sidebar_results --> + </div> +{% endblock %} diff --git a/searx/templates/oscar/search.html b/searx/templates/oscar/search.html index bf9a9af5f..cad9eca89 100644 --- a/searx/templates/oscar/search.html +++ b/searx/templates/oscar/search.html @@ -1,24 +1,24 @@ -{% from 'oscar/macros.html' import icon %}
-<form method="{{ method or 'POST' }}" action="{{ url_for('index') }}" id="search_form" role="search">
- <div class="row">
- <div class="col-xs-12 col-md-8">
- <div class="input-group search-margin">
- <input type="search" name="q" class="form-control" id="q" placeholder="{{ _('Search for...') }}" aria-label="{{ _('Search for...') }}" autocomplete="off" value="{{ q }}">
- <span class="input-group-btn">
- <button type="submit" class="btn btn-default" aria-label="{{ _('Start search') }}"><span class="hide_if_nojs">{{ icon('search') }}</span><span class="hidden active_if_nojs">{{ _('Start search') }}</span></button>
- </span>
- </div>
- </div>
- <div class="col-xs-6 col-md-2 search-margin">
- {% include 'oscar/time-range.html' %}
- </div>
- <div class="col-xs-6 col-md-2 search-margin">
- {% include 'oscar/languages.html' %}
- </div>
- </div>
- <div class="row">
- <div class="col-sm-12">
- {% include 'oscar/categories.html' %}
- </div>
- </div>
-</form><!-- / #search_form_full -->
+{% from 'oscar/macros.html' import icon %} +<form method="{{ method or 'POST' }}" action="{{ url_for('index') }}" id="search_form" role="search"> + <div class="row"> + <div class="col-xs-12 col-md-8"> + <div class="input-group search-margin"> + <input type="search" name="q" class="form-control" id="q" placeholder="{{ _('Search for...') }}" aria-label="{{ _('Search for...') }}" autocomplete="off" value="{{ q }}" accesskey="s"> + <span class="input-group-btn"> + <button type="submit" class="btn btn-default" aria-label="{{ _('Start search') }}"><span class="hide_if_nojs">{{ icon('search') }}</span><span class="hidden active_if_nojs">{{ _('Start search') }}</span></button> + </span> + </div> + </div> + <div class="col-xs-6 col-md-2 search-margin"> + {%- include 'oscar/time-range.html' -%} + </div> + <div class="col-xs-6 col-md-2 search-margin"> + {%- include 'oscar/languages.html' -%} + </div> + </div> + <div class="row"> + <div class="col-sm-12"> + {%- include 'oscar/categories.html' -%} + </div> + </div> +</form><!-- / #search_form_full --> diff --git a/searx/templates/oscar/search_full.html b/searx/templates/oscar/search_full.html index fd8a9a393..656463178 100644 --- a/searx/templates/oscar/search_full.html +++ b/searx/templates/oscar/search_full.html @@ -1,18 +1,18 @@ -{% from 'oscar/macros.html' import icon %}
-
-<form method="{{ method or 'POST' }}" action="{{ url_for('index') }}" id="search_form" role="search">
- {% if rtl %}
- <div class="input-group">
- {% else %}
- <div class="input-group col-md-8 col-md-offset-2">
- {% endif %}
- <input type="search" name="q" class="form-control input-lg autofocus" id="q" placeholder="{{ _('Search for...') }}" aria-label="{{ _('Search for...') }}" autocomplete="off" value="{{ q }}">
- <span class="input-group-btn">
- <button type="submit" class="btn btn-default input-lg" aria-label="{{ _('Start search') }}"><span class="hide_if_nojs">{{ icon('search') }}</span><span class="hidden active_if_nojs">{{ _('Start search') }}</span></button>
- </span>
- </div>
- <div class="col-md-8 col-md-offset-2 advanced">
- {% include 'oscar/advanced.html' %}
- </div>
-
-</form><!-- / #search_form_full -->
+{% from 'oscar/macros.html' import icon %} + +<form method="{{ method or 'POST' }}" action="{{ url_for('index') }}" id="search_form" role="search"> + {% if rtl %} + <div class="input-group"> + {% else %} + <div class="input-group col-md-8 col-md-offset-2"> + {% endif %} + <input type="search" name="q" class="form-control input-lg autofocus" id="q" placeholder="{{ _('Search for...') }}" aria-label="{{ _('Search for...') }}" autocomplete="off" value="{{ q }}" accesskey="s"> + <span class="input-group-btn"> + <button type="submit" class="btn btn-default input-lg" aria-label="{{ _('Start search') }}"><span class="hide_if_nojs">{{ icon('search') }}</span><span class="hidden active_if_nojs">{{ _('Start search') }}</span></button> + </span> + </div> + <div class="col-md-8 col-md-offset-2 advanced"> + {% include 'oscar/advanced.html' %} + </div> + +</form><!-- / #search_form_full --> diff --git a/searx/templates/oscar/time-range.html b/searx/templates/oscar/time-range.html index d5efe9182..fb1c0754b 100644 --- a/searx/templates/oscar/time-range.html +++ b/searx/templates/oscar/time-range.html @@ -1,17 +1,17 @@ -<select name="time_range" id="time-range" class="custom-select form-control"> +<select name="time_range" id="time-range" class="custom-select form-control" accesskey="t">{{- "" -}} <option id="time-range-anytime" value="" {{ "selected" if time_range=="" or not time_range else ""}}> - {{ _('Anytime') }} - </option> + {{- _('Anytime') -}} + </option>{{- "" -}} <option id="time-range-day" value="day" {{ "selected" if time_range=="day" else ""}}> - {{ _('Last day') }} - </option> + {{- _('Last day') -}} + </option>{{- "" -}} <option id="time-range-week" value="week" {{ "selected" if time_range=="week" else ""}}> - {{ _('Last week') }} - </option> + {{- _('Last week') -}} + </option>{{- "" -}} <option id="time-range-month" value="month" {{ "selected" if time_range=="month" else ""}}> - {{ _('Last month') }} - </option> + {{- _('Last month') -}} + </option>{{- "" -}} <option id="time-range-year" value="year" {{ "selected" if time_range=="year" else ""}}> - {{ _('Last year') }} - </option> + {{- _('Last year') -}} + </option>{{- "" -}} </select> diff --git a/searx/templates/simple/result_templates/torrent.html b/searx/templates/simple/result_templates/torrent.html index 3c7fd15e8..71c775bc9 100644 --- a/searx/templates/simple/result_templates/torrent.html +++ b/searx/templates/simple/result_templates/torrent.html @@ -6,7 +6,7 @@ {% if result.magnetlink %}<p class="altlink"> • {{ result_link(result.magnetlink, icon('magnet') + _('magnet link'), "magnetlink") }}</p>{% endif %} {% if result.torrentfile %}<p class="altlink"> • {{ result_link(result.torrentfile, icon('download-alt') + _('torrent file'), "torrentfile") }}</p>{% endif %} -{% if result.seed %}<p class="stat"> • {{ icon('arrow-swap') }} {{ _('Seeder') }} <span class="badge">{{ result.seed }}</span> • {{ _('Leecher') }} <span class="badge">{{ result.leech }}</span></p>{% endif %} +{% if result.seed is defined %}<p class="stat"> • {{ icon('arrow-swap') }} {{ _('Seeder') }} <span class="badge">{{ result.seed }}</span> • {{ _('Leecher') }} <span class="badge">{{ result.leech }}</span></p>{% endif %} {%- if result.filesize %}<p class="stat">{{ icon('floppy-disk') }} {{ _('Filesize') }}<span class="badge"> {%- if result.filesize < 1024 %}{{ result.filesize }} {{ _('Bytes') }} diff --git a/searx/utils.py b/searx/utils.py index e61a134f7..5ea9dc89c 100644 --- a/searx/utils.py +++ b/searx/utils.py @@ -13,6 +13,7 @@ from numbers import Number from os.path import splitext, join from io import open from random import choice +from lxml.etree import XPath import sys import json @@ -51,6 +52,7 @@ ecma_unescape2_re = re.compile(r'%([0-9a-fA-F]{2})', re.UNICODE) useragents = json.loads(open(os.path.dirname(os.path.realpath(__file__)) + "/data/useragents.json", 'r', encoding='utf-8').read()) +xpath_cache = dict() lang_to_lc_cache = dict() @@ -450,3 +452,16 @@ def get_engine_from_settings(name): return engine return {} + + +def get_xpath(xpath_str): + result = xpath_cache.get(xpath_str, None) + if result is None: + result = XPath(xpath_str) + xpath_cache[xpath_str] = result + return result + + +def eval_xpath(element, xpath_str): + xpath = get_xpath(xpath_str) + return xpath(element) diff --git a/searx/webapp.py b/searx/webapp.py index 3bb29140a..7cf4106d3 100644 --- a/searx/webapp.py +++ b/searx/webapp.py @@ -41,7 +41,10 @@ except: logger.critical("cannot import dependency: pygments") from sys import exit exit(1) -from cgi import escape +try: + from cgi import escape +except: + from html import escape from datetime import datetime, timedelta from time import time from werkzeug.contrib.fixers import ProxyFix @@ -154,20 +157,18 @@ outgoing_proxies = settings['outgoing'].get('proxies') or None @babel.localeselector def get_locale(): - locale = request.accept_languages.best_match(settings['locales'].keys()) - - if request.preferences.get_value('locale') != '': - locale = request.preferences.get_value('locale') + if 'locale' in request.form\ + and request.form['locale'] in settings['locales']: + return request.form['locale'] if 'locale' in request.args\ and request.args['locale'] in settings['locales']: - locale = request.args['locale'] + return request.args['locale'] - if 'locale' in request.form\ - and request.form['locale'] in settings['locales']: - locale = request.form['locale'] + if request.preferences.get_value('locale') != '': + return request.preferences.get_value('locale') - return locale + return request.accept_languages.best_match(settings['locales'].keys()) # code-highlighter @@ -11,14 +11,14 @@ import sys sys.path.insert(0, './searx') from version import VERSION_STRING +with open('README.rst') as f: + long_description = f.read() -def read(*rnames): - return open(os.path.join(os.path.dirname(__file__), *rnames)).read() +with open('requirements.txt') as f: + requirements = [ l.strip() for l in f.readlines()] - -long_description = read('README.rst') -requirements = map(str.strip, open('requirements.txt').readlines()) -dev_requirements = map(str.strip, open('requirements-dev.txt').readlines()) +with open('requirements-dev.txt') as f: + dev_requirements = [ l.strip() for l in f.readlines()] setup( name='searx', diff --git a/tests/unit/engines/test_google.py b/tests/unit/engines/test_google.py index a73e9d2be..9d0edd439 100644 --- a/tests/unit/engines/test_google.py +++ b/tests/unit/engines/test_google.py @@ -58,93 +58,50 @@ class TestGoogleEngine(SearxTestCase): self.assertEqual(google.response(response), []) html = """ - <div class="g"> - <h3 class="r"> - <a href="http://this.should.be.the.link/"> - <b>This</b> is <b>the</b> title - </a> - </h3> - <div class="s"> - <div class="kv" style="margin-bottom:2px"> - <cite> - <b>test</b>.psychologies.com/ - </cite> - <div class="_nBb"> - <div style="display:inline" onclick="google.sham(this);" aria-expanded="false" - aria-haspopup="true" tabindex="0" data-ved="0CBUQ7B0wAA"> - <span class="_O0"> - </span> + <div class="ZINbbc xpd O9g5cc uUPGi"> + <div> + <div class="kCrYT"> + <a href="/url?q=http://this.should.be.the.link/"> + <div class="BNeawe"> + <b>This</b> is <b>the</b> title </div> - <div style="display:none" class="am-dropdown-menu" role="menu" tabindex="-1"> - <ul> - <li class="_Ykb"> - <a class="_Zkb" href="http://www.google.fr/url?url=http://webcache.googleusercontent - .com/search%3Fcache:R1Z_4pGXjuIJ:http://test.psychologies.com/"> - En cache - </a> - </li> - <li class="_Ykb"> - <a class="_Zkb" href="/search?safe=off&q=related:test.psy.com/"> - Pages similaires - </a> - </li> - </ul> + <div class="BNeawe"> + http://website + </div> + </a> + </div> + <div class="kCrYT"> + <div> + <div class="BNeawe"> + <div> + <div class="BNeawe"> + This should be the content. + </div> + </div> </div> </div> </div> - <span class="st"> - This should be the content. - </span> - <br> - <div class="osl"> - <a href="http://www.google.fr/url?url=http://test.psychologies.com/tests/"> - Test Personnalité - </a> - - <a href="http://www.google.fr/url?url=http://test.psychologies.com/test/"> - Tests - Moi - </a> - - <a href="http://www.google.fr/url?url=http://test.psychologies.com/test/tests-couple"> - Test Couple - </a> - - - <a href="http://www.google.fr/url?url=http://test.psychologies.com/tests/tests-amour"> - Test Amour + </div> + </p> + <div class="ZINbbc xpd O9g5cc uUPGi"> + <div> + <div class="kCrYT"> + <span> + <div class="BNeawe"> + Related searches + </div> + </span> + </div> + <div class="rVLSBd"> + <a> + <div> + <div class="BNeawe"> + suggestion title + </div> + </div> </a> </div> </div> - </div> - <div class="g"> - <h3 class="r"> - <a href="http://www.google.com/images?q=toto"> - <b>This</b> - </a> - </h3> - </div> - <div class="g"> - <h3 class="r"> - <a href="http://www.google.com/search?q=toto"> - <b>This</b> is - </a> - </h3> - </div> - <div class="g"> - <h3 class="r"> - <a href="€"> - <b>This</b> is <b>the</b> - </a> - </h3> - </div> - <div class="g"> - <h3 class="r"> - <a href="/url?q=url"> - <b>This</b> is <b>the</b> - </a> - </h3> - </div> - <p class="_Bmc" style="margin:3px 8px"> - <a href="/search?num=20&safe=off&q=t&revid=1754833769&sa=X&ei=-&ved="> - suggestion <b>title</b> - </a> </p> """ response = self.mock_response(html) diff --git a/tests/unit/engines/test_seedpeer.py b/tests/unit/engines/test_seedpeer.py new file mode 100644 index 000000000..2057c1cb1 --- /dev/null +++ b/tests/unit/engines/test_seedpeer.py @@ -0,0 +1,66 @@ +# -*- coding: utf-8 -*- +from collections import defaultdict +import mock +from searx.engines import seedpeer +from searx.testing import SearxTestCase + + +class TestBtdiggEngine(SearxTestCase): + + def test_request(self): + query = 'test_query' + dicto = defaultdict(dict) + dicto['pageno'] = 1 + params = seedpeer.request(query, dicto) + self.assertIn('url', params) + self.assertIn(query, params['url']) + self.assertIn('seedpeer', params['url']) + + def test_response(self): + self.assertRaises(AttributeError, seedpeer.response, None) + self.assertRaises(AttributeError, seedpeer.response, []) + self.assertRaises(AttributeError, seedpeer.response, '') + self.assertRaises(AttributeError, seedpeer.response, '[]') + + response = mock.Mock(text='<html></html>') + self.assertEqual(seedpeer.response(response), []) + + html = u""" + <html> + <head> + <script></script> + <script type="text/javascript" src="not_here.js"></script> + <script type="text/javascript"> + window.initialData= + {"data": {"list": [{"name": "Title", "seeds": "10", "peers": "20", "size": "1024", "hash": "abc123"}]}} + </script> + </head> + <body> + <table></table> + <table> + <thead><tr></tr></thead> + <tbody> + <tr> + <td><a href="link">Title</a></td> + <td>1 year</td> + <td>1 KB</td> + <td>10</td> + <td>20</td> + <td></td> + </tr> + </tbody> + </table> + </body> + </html> + """ + response = mock.Mock(text=html) + results = seedpeer.response(response) + self.assertEqual(type(results), list) + self.assertEqual(len(results), 1) + self.assertEqual(results[0]['title'], 'Title') + self.assertEqual(results[0]['url'], 'https://seedpeer.me/link') + self.assertEqual(results[0]['seed'], 10) + self.assertEqual(results[0]['leech'], 20) + self.assertEqual(results[0]['filesize'], 1024) + self.assertEqual(results[0]['torrentfile'], 'https://seedpeer.me/torrent/abc123') + self.assertEqual(results[0]['magnetlink'], 'magnet:?xt=urn:btih:abc123') diff --git a/utils/makefile.include b/utils/makefile.include new file mode 100644 index 000000000..716889c02 --- /dev/null +++ b/utils/makefile.include @@ -0,0 +1,128 @@ +# -*- coding: utf-8; mode: makefile-gmake -*- + +make-help: + @echo ' make V=0|1 [targets] 0 => quiet build (default), 1 => verbose build' + @echo ' make V=2 [targets] 2 => give reason for rebuild of target' + +quiet_cmd_common_clean = CLEAN $@ + cmd_common_clean = \ + rm -rf tests/build ;\ + find . -name '*.orig' -exec rm -f {} + ;\ + find . -name '*.rej' -exec rm -f {} + ;\ + find . -name '*~' -exec rm -f {} + ;\ + find . -name '*.bak' -exec rm -f {} + ;\ + +FMT = cat +ifeq ($(shell which fmt >/dev/null 2>&1; echo $$?), 0) +FMT = fmt +endif + +# MS-Windows +# +# For a minimal *make-environment*, I'am using the gnu-tools from: +# +# - GNU MCU Eclipse Windows Build Tools, which brings 'make', 'rm' etc. +# https://github.com/gnu-mcu-eclipse/windows-build-tools/releases +# +# - git for Windows, which brings 'find', 'grep' etc. +# https://git-scm.com/download/win + + +# normpath +# +# System-dependent normalization of the path name +# +# usage: $(call normpath,/path/to/file) + +normpath = $1 +ifeq ($(OS),Windows_NT) + normpath = $(subst /,\,$1) +endif + + +# stolen from linux/Makefile +# + +ifeq ("$(origin V)", "command line") + KBUILD_VERBOSE = $(V) +endif +ifndef KBUILD_VERBOSE + KBUILD_VERBOSE = 0 +endif + +ifeq ($(KBUILD_VERBOSE),1) + quiet = + Q = +else + quiet=quiet_ + Q = @ +endif + +# stolen from linux/scripts/Kbuild.include +# + +# Convenient variables +comma := , +quote := " +#" this comment is only for emacs highlighting +squote := ' +#' this comment is only for emacs highlighting +empty := +space := $(empty) $(empty) +space_escape := _-_SPACE_-_ + +# Find any prerequisites that is newer than target or that does not exist. +# PHONY targets skipped in both cases. +any-prereq = $(filter-out $(PHONY),$?) $(filter-out $(PHONY) $(wildcard $^),$^) +# +### +# why - tell why a a target got build +# enabled by make V=2 +# Output (listed in the order they are checked): +# (1) - due to target is PHONY +# (2) - due to target missing +# (3) - due to: file1.h file2.h +# (4) - due to command line change +# (5) - due to missing .cmd file +# (6) - due to target not in $(targets) +# (1) PHONY targets are always build +# (2) No target, so we better build it +# (3) Prerequisite is newer than target +# (4) The command line stored in the file named dir/.target.cmd +# differed from actual command line. This happens when compiler +# options changes +# (5) No dir/.target.cmd file (used to store command line) +# (6) No dir/.target.cmd file and target not listed in $(targets) +# This is a good hint that there is a bug in the kbuild file +ifeq ($(KBUILD_VERBOSE),2) +why = \ + $(if $(filter $@, $(PHONY)),- due to target is PHONY, \ + $(if $(wildcard $@), \ + $(if $(strip $(any-prereq)),- due to: $(any-prereq), \ + $(if $(arg-check), \ + $(if $(cmd_$@),- due to command line change, \ + $(if $(filter $@, $(targets)), \ + - due to missing .cmd file, \ + - due to $(notdir $@) not in $$(targets) \ + ) \ + ) \ + ) \ + ), \ + - due to target missing \ + ) \ + ) + +echo-why = $(call escsq, $(strip $(why))) +endif +# +### +# Escape single quote for use in echo statements +escsq = $(subst $(squote),'\$(squote)',$1) +# +# echo command. +# Short version is used, if $(quiet) equals `quiet_', otherwise full one. +echo-cmd = $(if $($(quiet)cmd_$(1)),echo '$(call escsq,$($(quiet)cmd_$(1)))$(echo-why)';) +# +# printing commands +cmd = @$(echo-cmd) $(cmd_$(1)) + diff --git a/utils/makefile.python b/utils/makefile.python new file mode 100644 index 000000000..228eb3f80 --- /dev/null +++ b/utils/makefile.python @@ -0,0 +1,290 @@ +# -*- coding: utf-8; mode: makefile-gmake -*- + +# list of python packages (folders) or modules (files) of this build +PYOBJECTS ?= + +SITE_PYTHON ?=$(dir $(abspath $(lastword $(MAKEFILE_LIST))))site-python +export PYTHONPATH := $(SITE_PYTHON):$$PYTHONPATH + +# folder where the python distribution takes place +PYDIST ?= ./py_dist +# folder where the python intermediate build files take place +PYBUILD ?= ./py_build +# python version to use +PY ?=3 +PYTHON ?= python$(PY) +PIP ?= pip$(PY) +PIP_INST ?= --user + +# https://www.python.org/dev/peps/pep-0508/#extras +#PY_SETUP_EXTRAS ?= \[develop,test\] +PY_SETUP_EXTRAS ?= + +PYDEBUG ?= --pdb +PYLINT_RC ?= .pylintrc + +TEST_FOLDER ?= ./tests +TEST ?= . + +VTENV_OPTS = "--no-site-packages" +PY_ENV = ./local/py$(PY) +PY_ENV_BIN = $(PY_ENV)/bin +PY_ENV_ACT = . $(PY_ENV_BIN)/activate + +ifeq ($(OS),Windows_NT) + PYTHON = python + PY_ENV_BIN = $(PY_ENV)/Scripts + PY_ENV_ACT = $(PY_ENV_BIN)/activate +endif + +ifeq ($(PYTHON),python) + VIRTUALENV = virtualenv +else + VIRTUALENV = virtualenv --python=$(PYTHON) +endif + +ifeq ($(KBUILD_VERBOSE),1) + PIP_VERBOSE = + VIRTUALENV_VERBOSE = +else + PIP_VERBOSE = "-q" + VIRTUALENV_VERBOSE = "-q" +endif + +python-help:: + @echo 'makefile.python:' + @echo ' pyenv | pyenv[un]install' + @echo ' build $(PY_ENV) & [un]install python objects' + @echo ' targts using pyenv $(PY_ENV):' + @echo ' pylint - run pylint *linting*' + @echo ' pytest - run *tox* test on python objects' + @echo ' pydebug - run tests within a PDB debug session' + @echo ' pybuild - build python packages' + @echo ' pyclean - clean intermediate python objects' + @echo ' targets using system users environment:' + @echo ' py[un]install - [un]install python objects in editable mode' + @echo ' upload-pypi - upload $(PYDIST)/* files to PyPi' + @echo 'options:' + @echo ' make PY=2 [targets] => to eval targets with python 2 ($(PY))' + @echo ' make PIP_INST= => to set/unset pip install options ($(PIP_INST))' + @echo ' make TEST=. => choose test from $(TEST_FOLDER) (default "." runs all)' + @echo ' make DEBUG= => target "debug": do not invoke PDB on errors' + @echo ' make PY_SETUP_EXTRAS => also install extras_require from setup.py \[develop,test\]' + @echo ' when using target "pydebug", set breakpoints within py-source by adding::' + @echo ' DEBUG()' + +# ------------------------------------------------------------------------------ +# OS requirements +# ------------------------------------------------------------------------------ + +PHONY += msg-python-exe python-exe +msg-python-exe: + @echo "\n $(PYTHON) is required\n\n\ + Make sure you have $(PYTHON) installed, grab it from\n\ + https://www.python.org or install it from your package\n\ + manager. On debian based OS these requirements are\n\ + installed by::\n\n\ + sudo -H apt-get install $(PYTHON)\n" | $(FMT) + +ifeq ($(shell which $(PYTHON) >/dev/null 2>&1; echo $$?), 1) +python-exe: msg-python-exe + $(error The '$(PYTHON)' command was not found) +else +python-exe: + @: +endif + +msg-pip-exe: + @echo "\n $(PIP) is required\n\n\ + Make sure you have updated pip installed, grab it from\n\ + https://pip.pypa.io or install it from your package\n\ + manager. On debian based OS these requirements are\n\ + installed by::\n\n\ + sudo -H apt-get install python$(PY)-pip\n" | $(FMT) + +ifeq ($(shell which $(PIP) >/dev/null 2>&1; echo $$?), 1) +pip-exe: msg-pip-exe + $(error The '$(PIP)' command was not found) +else +pip-exe: + @: +endif + +PHONY += msg-virtualenv-exe virtualenv-exe +msg-virtualenv-exe: + @echo "\n virtualenv is required\n\n\ + Make sure you have an updated virtualenv installed, grab it from\n\ + https://virtualenv.pypa.io/en/stable/installation/ or install it\n\ + via pip by::\n\n\ + pip install --user https://github.com/pypa/virtualenv/tarball/master\n" | $(FMT) + +ifeq ($(shell which virtualenv >/dev/null 2>&1; echo $$?), 1) +virtualenv-exe: msg-virtualenv-exe + $(error The 'virtualenv' command was not found) +else +virtualenv-exe: + @: +endif + +# ------------------------------------------------------------------------------ +# commands +# ------------------------------------------------------------------------------ + +# $2 path to folder with setup.py, this uses pip from the OS +quiet_cmd_pyinstall = INSTALL $2 + cmd_pyinstall = $(PIP) $(PIP_VERBOSE) install $(PIP_INST) -e $2$(PY_SETUP_EXTRAS) + +# $2 path to folder with setup.py, this uses pip from pyenv (not OS!) +quiet_cmd_pyenvinstall = PYENV install $2 + cmd_pyenvinstall = $(PY_ENV_BIN)/pip $(PIP_VERBOSE) install -e $2$(PY_SETUP_EXTRAS) + +# Uninstall the package. Since pip does not uninstall the no longer needed +# depencies (something like autoremove) the depencies remain. + +# $2 package name to uninstall, this uses pip from the OS. +quiet_cmd_pyuninstall = UNINSTALL $2 + cmd_pyuninstall = $(PIP) $(PIP_VERBOSE) uninstall --yes $2 + +# $2 path to folder with setup.py, this uses pip from pyenv (not OS!) +quiet_cmd_pyenvuninstall = PYENV uninstall $2 + cmd_pyenvuninstall = $(PY_ENV_BIN)/pip $(PIP_VERBOSE) uninstall --yes $2 + +# $2 path to folder where virtualenv take place +quiet_cmd_virtualenv = PYENV usage: $ source ./$@/bin/activate + cmd_virtualenv = \ + if [ ! -d "./$(PY_ENV)" ];then \ + $(VIRTUALENV) $(VIRTUALENV_VERBOSE) $(VTENV_OPTS) $2; \ + else \ + echo " PYENV using virtualenv from $2"; \ + fi + +# $2 path to lint +quiet_cmd_pylint = LINT $@ + cmd_pylint = $(PY_ENV_BIN)/pylint --rcfile $(PYLINT_RC) $2 + +quiet_cmd_pytest = TEST $@ + cmd_pytest = $(PY_ENV_BIN)/tox -vv + +# setuptools, pip, easy_install its a mess full of cracks, a documentation hell +# and broken by design ... all sucks, I really, really hate all this ... aaargh! +# +# About python packaging see `Python Packaging Authority`_. Most of the names +# here are mapped to ``setup(<name1>=..., <name2>=...)`` arguments in +# ``setup.py``. See `Packaging and distributing projects`_ about ``setup(...)`` +# arguments. If this is all new for you, start with `PyPI Quick and Dirty`_. +# +# Further read: +# +# - pythonwheels_ +# - setuptools_ +# - packaging_ +# - sdist_ +# - installing_ +# +# .. _`Python Packaging Authority`: https://www.pypa.io +# .. _`Packaging and distributing projects`: https://packaging.python.org/guides/distributing-packages-using-setuptools/ +# .. _`PyPI Quick and Dirty`: https://hynek.me/articles/sharing-your-labor-of-love-pypi-quick-and-dirty/ +# .. _pythonwheels: https://pythonwheels.com/ +# .. _setuptools: https://setuptools.readthedocs.io/en/latest/setuptools.html +# .. _packaging: https://packaging.python.org/guides/distributing-packages-using-setuptools/#packaging-and-distributing-projects +# .. _sdist: https://packaging.python.org/guides/distributing-packages-using-setuptools/#source-distributions +# .. _bdist_wheel: https://packaging.python.org/guides/distributing-packages-using-setuptools/#pure-python-wheels +# .. _installing: https://packaging.python.org/tutorials/installing-packages/ +# +quiet_cmd_pybuild = BUILD $@ + cmd_pybuild = $(PY_ENV_BIN)/$(PYTHON) setup.py \ + sdist -d $(PYDIST) \ + bdist_wheel --bdist-dir $(PYBUILD) -d $(PYDIST) + +quiet_cmd_pyclean = CLEAN $@ +# remove 'build' folder since bdist_wheel does not care the --bdist-dir + cmd_pyclean = \ + rm -rf $(PYDIST) $(PYBUILD) ./local ./.tox *.egg-info ;\ + find . -name '*.pyc' -exec rm -f {} + ;\ + find . -name '*.pyo' -exec rm -f {} + ;\ + find . -name __pycache__ -exec rm -rf {} + + +# ------------------------------------------------------------------------------ +# targets +# ------------------------------------------------------------------------------ + +# for installation use the pip from the OS! +PHONY += pyinstall +pyinstall: pip-exe + $(call cmd,pyinstall,.) + +PHONY += pyuninstall +pyuninstall: pip-exe + $(call cmd,pyuninstall,$(PYOBJECTS)) + +# for installation use the pip from PY_ENV (not the OS)! +PHONY += pyenvinstall +pyenvinstall: $(PY_ENV) + $(call cmd,pyenvinstall,.) + +PHONY += pyenvuninstall +pyenvuninstall: $(PY_ENV) + $(call cmd,pyenvuninstall,$(PYOBJECTS)) + +PHONY += pyclean +pyclean: + $(call cmd,pyclean) + +# to build *local* environment, python and virtualenv from the OS is needed! +pyenv: $(PY_ENV) +$(PY_ENV): virtualenv-exe python-exe + $(call cmd,virtualenv,$(PY_ENV)) + @$(PY_ENV_BIN)/pip install $(PIP_VERBOSE) -r requirements.txt + +PHONY += pylint-exe +pylint-exe: $(PY_ENV) + @$(PY_ENV_BIN)/pip $(PIP_VERBOSE) install pylint + +PHONY += pylint +pylint: pylint-exe + $(call cmd,pylint,$(PYOBJECTS)) + +PHONY += pybuild +pybuild: $(PY_ENV) + $(call cmd,pybuild) + +PHONY += pytest +pytest: $(PY_ENV) + $(call cmd,pytest) + +PHONY += pydebug +# set breakpoint with: +# DEBUG() +# e.g. to run tests in debug mode in emacs use: +# 'M-x pdb' ... 'make pydebug' +pydebug: $(PY_ENV) + DEBUG=$(DEBUG) $(PY_ENV_BIN)/pytest $(DEBUG) -v $(TEST_FOLDER)/$(TEST) + +# install / uninstall python objects into virtualenv (PYENV) +pyenv-install: $(PY_ENV) + @$(PY_ENV_BIN)/pip $(PIP_VERBOSE) install -e . + @echo " ACTIVATE $(call normpath,$(PY_ENV_ACT)) " + +pyenv-uninstall: $(PY_ENV) + @$(PY_ENV_BIN)/pip $(PIP_VERBOSE) uninstall --yes . + +# runs python interpreter from ./local/py<N>/bin/python +pyenv-python: pyenv-install + cd ./local; ../$(PY_ENV_BIN)/python -i + +# With 'dependency_links=' setuptools supports dependencies on packages hosted +# on other reposetories then PyPi, see "Packages Not On PyPI" [1]. The big +# drawback is, due to security reasons (I don't know where the security gate on +# PyPi is), this feature is not supported by pip [2]. Thats why an upload to +# PyPi is required and since uploads via setuptools is not recommended, we have +# to imstall / use twine ... its really a mess. +# +# [1] http://python-packaging.readthedocs.io/en/latest/dependencies.html#packages-not-on-pypi +# [2] https://github.com/pypa/pip/pull/1519 + +# https://github.com/pypa/twine +PHONY += upload-pypi +upload-pypi: pyclean pybuild + @$(PY_ENV_BIN)/twine upload $(PYDIST)/* + +.PHONY: $(PHONY) |