add multi theming support
4
Makefile
|
@ -44,13 +44,13 @@ minimal: bin/buildout minimal.cfg setup.py
|
||||||
bin/buildout -c minimal.cfg $(options)
|
bin/buildout -c minimal.cfg $(options)
|
||||||
|
|
||||||
styles:
|
styles:
|
||||||
@lessc -x searx/static/less/style.less > searx/static/css/style.css
|
@lessc -x searx/static/default/less/style.less > searx/static/default/css/style.css
|
||||||
|
|
||||||
locales:
|
locales:
|
||||||
@pybabel compile -d searx/translations
|
@pybabel compile -d searx/translations
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
@rm -rf .installed.cfg .mr.developer.cfg bin parts develop-eggs \
|
@rm -rf .installed.cfg .mr.developer.cfg bin parts develop-eggs \
|
||||||
searx.egg-info lib include .coverage coverage searx/static/css/*.css
|
searx.egg-info lib include .coverage coverage searx/static/default/css/*.css
|
||||||
|
|
||||||
.PHONY: all tests robot flake8 coverage production minimal styles locales clean
|
.PHONY: all tests robot flake8 coverage production minimal styles locales clean
|
||||||
|
|
|
@ -4,6 +4,8 @@ server:
|
||||||
debug : True
|
debug : True
|
||||||
request_timeout : 2.0 # seconds
|
request_timeout : 2.0 # seconds
|
||||||
base_url : False
|
base_url : False
|
||||||
|
themes_path : ""
|
||||||
|
default_theme : default
|
||||||
|
|
||||||
engines:
|
engines:
|
||||||
- name : wikipedia
|
- name : wikipedia
|
||||||
|
|
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 3.1 KiB |
Before Width: | Height: | Size: 7.6 KiB After Width: | Height: | Size: 7.6 KiB |
Before Width: | Height: | Size: 6.4 KiB After Width: | Height: | Size: 6.4 KiB |
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 6.4 KiB After Width: | Height: | Size: 6.4 KiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 837 B After Width: | Height: | Size: 837 B |
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.2 KiB |
Before Width: | Height: | Size: 7.5 KiB After Width: | Height: | Size: 7.5 KiB |
Before Width: | Height: | Size: 7.1 KiB After Width: | Height: | Size: 7.1 KiB |
|
@ -1,6 +1,6 @@
|
||||||
{% extends 'base.html' %}
|
{% extends 'default/base.html' %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
{% include 'github_ribbon.html' %}
|
{% include 'default/github_ribbon.html' %}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<h1>About <a href="{{ url_for('index') }}">searx</a></h1>
|
<h1>About <a href="{{ url_for('index') }}">searx</a></h1>
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
{% extends "base.html" %}
|
{% extends "default/base.html" %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="center">
|
<div class="center">
|
||||||
<div class="title"><h1>searx</h1></div>
|
<div class="title"><h1>searx</h1></div>
|
||||||
{% include 'search.html' %}
|
{% include 'default/search.html' %}
|
||||||
<p class="top_margin">
|
<p class="top_margin">
|
||||||
<a href="{{ url_for('about') }}" class="hmarg">{{ _('about') }}</a>
|
<a href="{{ url_for('about') }}" class="hmarg">{{ _('about') }}</a>
|
||||||
<a href="{{ url_for('preferences') }}" class="hmarg">{{ _('preferences') }}</a>
|
<a href="{{ url_for('preferences') }}" class="hmarg">{{ _('preferences') }}</a>
|
|
@ -1,4 +1,4 @@
|
||||||
{% extends "base.html" %}
|
{% extends "default/base.html" %}
|
||||||
{% block head %} {% endblock %}
|
{% block head %} {% endblock %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
@ -8,7 +8,7 @@
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<legend>{{ _('Default categories') }}</legend>
|
<legend>{{ _('Default categories') }}</legend>
|
||||||
<p>
|
<p>
|
||||||
{% include 'categories.html' %}
|
{% include 'default/categories.html' %}
|
||||||
</p>
|
</p>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<fieldset>
|
<fieldset>
|
||||||
|
@ -52,6 +52,16 @@
|
||||||
</select>
|
</select>
|
||||||
</p>
|
</p>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
<fieldset>
|
||||||
|
<legend>{{ _('Themes') }}</legend>
|
||||||
|
<p>
|
||||||
|
<select name="theme">
|
||||||
|
{% for name in themes %}
|
||||||
|
<option value="{{ name }}" {% if name == theme %}selected="selected"{% endif %}>{{ name }}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</p>
|
||||||
|
</fieldset>
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<legend>{{ _('Currently used search engines') }}</legend>
|
<legend>{{ _('Currently used search engines') }}</legend>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<div class="result {{ result.class }}">
|
<div class="result {{ result.class }}">
|
||||||
|
|
||||||
{% if result['favicon'] %}
|
{% if result['favicon'] %}
|
||||||
<img width="14" height="14" class="favicon" src="static/img/icon_{{result['favicon']}}.ico" />
|
<img width="14" height="14" class="favicon" src="static/{{theme}}/img/icon_{{result['favicon']}}.ico" />
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<div>
|
<div>
|
|
@ -1,6 +1,6 @@
|
||||||
<div class="result">
|
<div class="result">
|
||||||
{% if result['favicon'] %}
|
{% if result['favicon'] %}
|
||||||
<img width="14" height="14" class="favicon" src="static/img/icon_{{result['favicon']}}.ico" />
|
<img width="14" height="14" class="favicon" src="static/{{theme}}/img/icon_{{result['favicon']}}.ico" />
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<p>
|
<p>
|
|
@ -1,9 +1,9 @@
|
||||||
{% extends "base.html" %}
|
{% extends "default/base.html" %}
|
||||||
{% block title %}{{ q }} - {% endblock %}
|
{% block title %}{{ q }} - {% endblock %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="right"><a href="{{ url_for('preferences') }}" id="preferences"><span>preferences</span></a></div>
|
<div class="right"><a href="{{ url_for('preferences') }}" id="preferences"><span>preferences</span></a></div>
|
||||||
<div class="small search center">
|
<div class="small search center">
|
||||||
{% include 'search.html' %}
|
{% include 'default/search.html' %}
|
||||||
</div>
|
</div>
|
||||||
<div id="results">
|
<div id="results">
|
||||||
<div id="sidebar">
|
<div id="sidebar">
|
||||||
|
@ -43,9 +43,9 @@
|
||||||
|
|
||||||
{% for result in results %}
|
{% for result in results %}
|
||||||
{% if result['template'] %}
|
{% if result['template'] %}
|
||||||
{% include 'result_templates/'+result['template'] %}
|
{% include 'default/result_templates/'+result['template'] %}
|
||||||
{% else %}
|
{% else %}
|
||||||
{% include 'result_templates/default.html' %}
|
{% include 'default/result_templates/default.html' %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
|
@ -3,5 +3,5 @@
|
||||||
<input type="text" placeholder="{{ _('Search for...') }}" id="q" class="q" name="q" tabindex="1" autocomplete="off" {% if q %}value="{{ q }}"{% endif %}/>
|
<input type="text" placeholder="{{ _('Search for...') }}" id="q" class="q" name="q" tabindex="1" autocomplete="off" {% if q %}value="{{ q }}"{% endif %}/>
|
||||||
<input type="submit" value="search" id="search_submit" />
|
<input type="submit" value="search" id="search_submit" />
|
||||||
</div>
|
</div>
|
||||||
{% include 'categories.html' %}
|
{% include 'default/categories.html' %}
|
||||||
</form>
|
</form>
|
|
@ -1,4 +1,4 @@
|
||||||
{% extends "base.html" %}
|
{% extends "default/base.html" %}
|
||||||
{% block head %} {% endblock %}
|
{% block head %} {% endblock %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h2>{{ _('Engine stats') }}</h2>
|
<h2>{{ _('Engine stats') }}</h2>
|
|
@ -1,11 +1,13 @@
|
||||||
from HTMLParser import HTMLParser
|
|
||||||
#import htmlentitydefs
|
#import htmlentitydefs
|
||||||
import csv
|
|
||||||
from codecs import getincrementalencoder
|
from codecs import getincrementalencoder
|
||||||
import cStringIO
|
from HTMLParser import HTMLParser
|
||||||
import re
|
|
||||||
from random import choice
|
from random import choice
|
||||||
|
|
||||||
|
import cStringIO
|
||||||
|
import csv
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
|
||||||
ua_versions = ('26.0', '27.0', '28.0')
|
ua_versions = ('26.0', '27.0', '28.0')
|
||||||
ua_os = ('Windows NT 6.3; WOW64',
|
ua_os = ('Windows NT 6.3; WOW64',
|
||||||
'X11; Linux x86_64',
|
'X11; Linux x86_64',
|
||||||
|
@ -110,3 +112,17 @@ class UnicodeWriter:
|
||||||
def writerows(self, rows):
|
def writerows(self, rows):
|
||||||
for row in rows:
|
for row in rows:
|
||||||
self.writerow(row)
|
self.writerow(row)
|
||||||
|
|
||||||
|
|
||||||
|
def get_themes(root):
|
||||||
|
"""Returns available themes list."""
|
||||||
|
|
||||||
|
static_path = os.path.join(root, 'static')
|
||||||
|
static_names = set(os.listdir(static_path))
|
||||||
|
templates_path = os.path.join(root, 'templates')
|
||||||
|
templates_names = set(os.listdir(templates_path))
|
||||||
|
|
||||||
|
themes = []
|
||||||
|
for name in static_names.intersection(templates_names):
|
||||||
|
themes += [name]
|
||||||
|
return static_path, templates_path, themes
|
||||||
|
|
|
@ -38,16 +38,23 @@ from searx.engines import (
|
||||||
search as do_search, categories, engines, get_engines_stats,
|
search as do_search, categories, engines, get_engines_stats,
|
||||||
engine_shortcuts
|
engine_shortcuts
|
||||||
)
|
)
|
||||||
from searx.utils import UnicodeWriter, highlight_content, html_to_text
|
from searx.utils import (
|
||||||
|
UnicodeWriter, highlight_content, html_to_text, get_themes
|
||||||
|
)
|
||||||
from searx.languages import language_codes
|
from searx.languages import language_codes
|
||||||
from searx.search import Search
|
from searx.search import Search
|
||||||
from searx.autocomplete import backends as autocomplete_backends
|
from searx.autocomplete import backends as autocomplete_backends
|
||||||
|
|
||||||
|
|
||||||
|
static_path, templates_path, themes = get_themes(settings['themes_path'] if \
|
||||||
|
settings.get('themes_path', None) else searx_dir)
|
||||||
|
default_theme = settings['default_theme'] if \
|
||||||
|
settings.get('default_theme', None) else 'default'
|
||||||
|
|
||||||
app = Flask(
|
app = Flask(
|
||||||
__name__,
|
__name__,
|
||||||
static_folder=os.path.join(searx_dir, 'static'),
|
static_folder=static_path,
|
||||||
template_folder=os.path.join(searx_dir, 'templates')
|
template_folder=templates_path
|
||||||
)
|
)
|
||||||
|
|
||||||
app.secret_key = settings['server']['secret_key']
|
app.secret_key = settings['server']['secret_key']
|
||||||
|
@ -90,7 +97,30 @@ def get_base_url():
|
||||||
return hostname
|
return hostname
|
||||||
|
|
||||||
|
|
||||||
def render(template_name, **kwargs):
|
def get_current_theme_name(override=None):
|
||||||
|
"""Returns theme name.
|
||||||
|
|
||||||
|
Checks in this order:
|
||||||
|
1. override
|
||||||
|
2. cookies
|
||||||
|
3. settings"""
|
||||||
|
|
||||||
|
if override and override in themes:
|
||||||
|
return override
|
||||||
|
theme_name = request.cookies.get('theme', default_theme)
|
||||||
|
if theme_name not in themes:
|
||||||
|
theme_name = default_theme
|
||||||
|
return theme_name
|
||||||
|
|
||||||
|
|
||||||
|
def url_for_theme(endpoint, override_theme=None, **values):
|
||||||
|
if endpoint == 'static' and values.get('filename', None):
|
||||||
|
theme_name = get_current_theme_name(override=override_theme)
|
||||||
|
values['filename'] = "{}/{}".format(theme_name, values['filename'])
|
||||||
|
return url_for(endpoint, **values)
|
||||||
|
|
||||||
|
|
||||||
|
def render(template_name, override_theme=None, **kwargs):
|
||||||
blocked_engines = request.cookies.get('blocked_engines', '').split(',')
|
blocked_engines = request.cookies.get('blocked_engines', '').split(',')
|
||||||
|
|
||||||
autocomplete = request.cookies.get('autocomplete')
|
autocomplete = request.cookies.get('autocomplete')
|
||||||
|
@ -125,7 +155,13 @@ def render(template_name, **kwargs):
|
||||||
|
|
||||||
kwargs['method'] = request.cookies.get('method', 'POST')
|
kwargs['method'] = request.cookies.get('method', 'POST')
|
||||||
|
|
||||||
return render_template(template_name, **kwargs)
|
# override url_for function in templates
|
||||||
|
kwargs['url_for'] = url_for_theme
|
||||||
|
|
||||||
|
kwargs['theme'] = get_current_theme_name(override=override_theme)
|
||||||
|
|
||||||
|
return render_template(
|
||||||
|
'{}/{}'.format(kwargs['theme'], template_name), **kwargs)
|
||||||
|
|
||||||
|
|
||||||
@app.route('/search', methods=['GET', 'POST'])
|
@app.route('/search', methods=['GET', 'POST'])
|
||||||
|
@ -232,7 +268,8 @@ def index():
|
||||||
paging=search.paging,
|
paging=search.paging,
|
||||||
pageno=search.pageno,
|
pageno=search.pageno,
|
||||||
base_url=get_base_url(),
|
base_url=get_base_url(),
|
||||||
suggestions=search.suggestions
|
suggestions=search.suggestions,
|
||||||
|
theme=get_current_theme_name()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -290,7 +327,7 @@ def preferences():
|
||||||
|
|
||||||
if request.method == 'GET':
|
if request.method == 'GET':
|
||||||
blocked_engines = request.cookies.get('blocked_engines', '').split(',')
|
blocked_engines = request.cookies.get('blocked_engines', '').split(',')
|
||||||
else:
|
else: # on save
|
||||||
selected_categories = []
|
selected_categories = []
|
||||||
locale = None
|
locale = None
|
||||||
autocomplete = ''
|
autocomplete = ''
|
||||||
|
@ -315,6 +352,8 @@ def preferences():
|
||||||
engine_name = pd_name.replace('engine_', '', 1)
|
engine_name = pd_name.replace('engine_', '', 1)
|
||||||
if engine_name in engines:
|
if engine_name in engines:
|
||||||
blocked_engines.append(engine_name)
|
blocked_engines.append(engine_name)
|
||||||
|
elif pd_name == 'theme':
|
||||||
|
theme = pd if pd in themes else default_theme
|
||||||
|
|
||||||
resp = make_response(redirect(url_for('index')))
|
resp = make_response(redirect(url_for('index')))
|
||||||
|
|
||||||
|
@ -352,6 +391,9 @@ def preferences():
|
||||||
|
|
||||||
resp.set_cookie('method', method, max_age=cookie_max_age)
|
resp.set_cookie('method', method, max_age=cookie_max_age)
|
||||||
|
|
||||||
|
resp.set_cookie(
|
||||||
|
'theme', theme, max_age=cookie_max_age)
|
||||||
|
|
||||||
return resp
|
return resp
|
||||||
return render('preferences.html',
|
return render('preferences.html',
|
||||||
locales=settings['locales'],
|
locales=settings['locales'],
|
||||||
|
@ -361,7 +403,9 @@ def preferences():
|
||||||
categs=categories.items(),
|
categs=categories.items(),
|
||||||
blocked_engines=blocked_engines,
|
blocked_engines=blocked_engines,
|
||||||
autocomplete_backends=autocomplete_backends,
|
autocomplete_backends=autocomplete_backends,
|
||||||
shortcuts={y: x for x, y in engine_shortcuts.items()})
|
shortcuts={y: x for x, y in engine_shortcuts.items()},
|
||||||
|
themes=themes,
|
||||||
|
theme=get_current_theme_name())
|
||||||
|
|
||||||
|
|
||||||
@app.route('/stats', methods=['GET'])
|
@app.route('/stats', methods=['GET'])
|
||||||
|
@ -404,7 +448,10 @@ def opensearch():
|
||||||
|
|
||||||
@app.route('/favicon.ico')
|
@app.route('/favicon.ico')
|
||||||
def favicon():
|
def favicon():
|
||||||
return send_from_directory(os.path.join(app.root_path, 'static/img'),
|
return send_from_directory(os.path.join(app.root_path,
|
||||||
|
'static',
|
||||||
|
get_current_theme_name(),
|
||||||
|
'img'),
|
||||||
'favicon.png',
|
'favicon.png',
|
||||||
mimetype='image/vnd.microsoft.icon')
|
mimetype='image/vnd.microsoft.icon')
|
||||||
|
|
||||||
|
|