[feat] allow customization of logo and favicon

also removed searxng-wordmark from templates directory and build
files, because it is no longer used anywhere
This commit is contained in:
shreven 2024-12-10 14:32:18 -05:00
parent 0245e82bd2
commit 0c202474c4
15 changed files with 92 additions and 12 deletions

View file

@ -174,3 +174,4 @@ features or generally made searx better:
- @micsthepick
- Daniel Kukula `<https://github.com/dkuku>`
- Patrick Evans `https://github.com/holysoles`
- shreven `<https://shreven.org>`_ B8B56F6FC0EADCA5B6177BC5F599020F48EE6F97

View file

@ -11,6 +11,10 @@
docs_url: https://docs.searxng.org
public_instances: https://searx.space
wiki_url: https://github.com/searxng/searxng/wiki
custom_files:
logo: /path/to/file.png
favicon_png: /path/to/file.png
favicon_svg: /path/to/file.svg
``issue_url`` :
If you host your own issue tracker change this URL.
@ -23,3 +27,12 @@
``wiki_url`` :
Link to your wiki (or ``false``)
``custom_files.logo`` :
Filepath to a custom logo. Be sure it has the right file extension, as that's used to determine the mimetype.
``custom_files.favicon_png`` :
Filepath to a custom PNG favicon.
``custom_files.favicon_svg`` :
Filepath to a custom SVG favicon. When using, be sure to also set ``custom_files.favicon_png``, as some browsers still don't support SVG favicons.

View file

@ -29,6 +29,12 @@ brand:
# links:
# Uptime: https://uptime.searxng.org/history/darmarit-org
# About: "https://searxng.org"
# custom_files:
# # If using custom logo, be sure to set the right file extension as it's used to determine the mimetype.
# logo: /path/to/file.png
# # For custom favicons, you must include a PNG version, as some browsers still don't support SVG favicons.
# favicon_png: /path/to/file.png
# favicon_svg: /path/to/file.svg
search:
# Filter results. 0: None, 1: Moderate, 2: Strict

View file

@ -152,6 +152,7 @@ SCHEMA = {
'public_instances': SettingsValue((False, str), 'https://searx.space'),
'wiki_url': SettingsValue(str, 'https://github.com/searxng/searxng/wiki'),
'custom': SettingsValue(dict, {'links': {}}),
'custom_files': SettingsValue(dict, {}),
},
'search': {
'safe_search': SettingsValue((0, 1, 2), 0),

View file

@ -151,7 +151,6 @@ module.exports = function (grunt) {
svgo: ['--config', 'svg4web.svgo.js']
},
files: {
'<%= _templates %>/simple/searxng-wordmark.min.svg': '<%= _brand %>/searxng-wordmark.svg',
'img/searxng.svg': '<%= _brand %>/searxng.svg',
'img/img_load_error.svg': '<%= _brand %>/img_load_error.svg'
}

View file

@ -6,7 +6,11 @@
text-align: center;
.title {
background: url('../img/searxng.png') no-repeat;
&.custom_logo {
background-image: url('../../../../custom/logo');
}
background-image: url('../img/searxng.png');
background-repeat: no-repeat;
min-height: 4rem;
margin: 4rem auto;
background-position: center;

View file

@ -27,9 +27,11 @@
{% block head %}
<link title="{{ instance_name }}" type="application/opensearchdescription+xml" rel="search" href="{{ opensearch_url }}">
{% endblock %}
<link rel="icon" href="{{ url_for('static', filename='img/favicon.png') }}" sizes="any">
<link rel="icon" href="{{ url_for('static', filename='img/favicon.svg') }}" type="image/svg+xml">
<link rel="apple-touch-icon" href="{{ url_for('static', filename='img/favicon.png') }}">
<link rel="icon" href="{{ url_for('custom', custom_file='favicon_png', filename='img/favicon.png') }}" sizes="any">
<link rel="apple-touch-icon" href="{{ url_for('custom', custom_file='favicon_png', filename='img/favicon.png') }}">
{% if not get_setting('brand.custom_files.favicon_png', false) or get_setting('brand.custom_files.favicon_svg', false) %}
<link rel="icon" href="{{ url_for('custom', custom_file='favicon_svg', filename='img/favicon.svg') }}" type="image/svg+xml">
{% endif %}
</head>
<body class="{{ endpoint }}_endpoint" >
<main id="main_{{ self._TemplateReference__context.name|replace("simple/", "")|replace(".html", "") }}" class="{{body_class}}">

View file

@ -2,7 +2,7 @@
{% from 'simple/icons.html' import icon_big %}
{% block content %}
<div class="index">
<div class="title"><h1>SearXNG</h1></div>
<div class="title{% if get_setting('brand.custom_files.logo', false) %} custom_logo{% endif %}"><h1>SearXNG</h1></div>
{% include 'simple/simple_search.html' %}
</div>
{% endblock %}

View file

@ -4,7 +4,7 @@
<LongName>SearXNG metasearch</LongName>
<Description>SearXNG is a metasearch engine that respects your privacy.</Description>
<InputEncoding>UTF-8</InputEncoding>
<Image type="image/png">{{ url_for('static', filename='img/favicon.png', _external=True) }}</Image>
<Image type="image/png">{{ url_for('custom', custom_file='favicon_png', filename='img/favicon.png', _external=True) }}</Image>
{% if opensearch_method == 'GET' %}
<Url rel="results" type="text/html" method="{{ opensearch_method }}" template="{{ url_for('search', _external=True) }}?q={searchTerms}"/>
{% else %}

View file

@ -2,6 +2,6 @@
{%- extends "simple/base.html" -%}
{%- block header -%}
<a href="{{ url_for('index') }}">{{- '' -}}
<img class="logo" src="{{ url_for('static', filename='img/searxng.png') }}" alt="SearXNG">{{- '' -}}
<img class="logo" src="{{ url_for('custom', custom_file='logo', filename='img/searxng.png') }}" alt="SearXNG">{{- '' -}}
</a>{{- '' -}}
{%- endblock -%}

View file

@ -2,7 +2,12 @@
<div id="search_header">
<a id="search_logo" href="{{ url_for('index') }}" tabindex="0" title="{{ _('Display the front page') }}">
<span hidden>SearXNG</span>
{% include 'simple/searxng-wordmark.min.svg' without context %}
<img width="23px" height="23px" src="
{%- if not get_setting('brand.custom_files.favicon_svg', false) -%}
{{ url_for('custom', custom_file='favicon_png', filename='img/favicon.svg') }}
{%- else -%} {{ url_for('custom', custom_file='favicon_svg', filename='img/favicon.svg') }}
{%- endif -%}
"></img>
</a>
<div id="search_view">
<div class="search_box">

View file

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="92mm" height="92mm" viewBox="0 0 92 92"><g transform="translate(-40.921 -17.417)"><circle cx="75.921" cy="53.903" r="30" fill="none" fill-opacity="1" stroke="#3050ff" stroke-width="10" stroke-miterlimit="4" stroke-dasharray="none" stroke-opacity="1"/><path d="M67.515 37.915a18 18 0 0 1 21.051 3.313 18 18 0 0 1 3.138 21.078" fill="none" fill-opacity="1" stroke="#3050ff" stroke-width="5" stroke-miterlimit="4" stroke-dasharray="none" stroke-opacity="1"/><rect width="18.846" height="39.963" x="3.706" y="122.09" ry="0" transform="rotate(-46.235)" opacity="1" fill="#3050ff" fill-opacity="1" stroke="none" stroke-width="8" stroke-miterlimit="4" stroke-dasharray="none" stroke-opacity="1"/></g></svg>

Before

Width:  |  Height:  |  Size: 746 B

View file

@ -37,6 +37,7 @@ from flask import (
make_response,
redirect,
send_from_directory,
send_file,
)
from flask.wrappers import Response
from flask.json import jsonify
@ -74,6 +75,7 @@ from searx import webutils
from searx.webutils import (
highlight_content,
get_static_files,
get_custom_files,
get_result_templates,
get_themes,
exception_classname_to_text,
@ -132,6 +134,7 @@ if not searx_debug and settings['server']['secret_key'] == 'ultrasecretkey':
# about static
logger.debug('static directory is %s', settings['ui']['static_path'])
static_files = get_static_files(settings['ui']['static_path'])
custom_files = get_custom_files()
# about templates
logger.debug('templates directory is %s', settings['ui']['templates_path'])
@ -256,6 +259,15 @@ def get_result_template(theme_name: str, template_name: str):
def custom_url_for(endpoint: str, **values):
suffix = ""
if endpoint == 'custom' and values.get('custom_file'):
if values['custom_file'] in custom_files:
values.pop('filename')
if get_setting('ui.static_use_hash'):
suffix = "?" + custom_files[values['custom_file']]['hash']
else:
# if there's no custom file defined in settings.yml, then use the default file
values.pop('custom_file')
endpoint = 'static'
if endpoint == 'static' and values.get('filename'):
file_hash = static_files.get(values['filename'])
if not file_hash:
@ -1279,8 +1291,25 @@ def opensearch():
return resp
@app.route('/custom/<path:custom_file>')
def custom(custom_file):
if custom_file in custom_files:
filepath = custom_files[custom_file]['path']
if os.path.isfile(filepath):
if 'mimetype' in custom_files[custom_file]:
return send_file(filepath, mimetype=custom_files[custom_file]['mimetype'])
return send_file(filepath)
logger.warning('%s does not exist or is not a file', filepath)
return flask.abort(404)
@app.route('/favicon.ico')
def favicon():
if 'favicon_png' in custom_files:
filepath = custom_files['favicon_png']['path']
if os.path.isfile(filepath):
return send_file(filepath, mimetype='image/png')
logger.warning('%s does not exist or is not a file', filepath)
theme = request.preferences.get_value("theme")
return send_from_directory(
os.path.join(app.root_path, settings['ui']['static_path'], 'themes', theme, 'img'), # pyright: ignore

View file

@ -204,6 +204,28 @@ def get_static_files(static_path: str) -> Dict[str, str]:
return static_files
def get_custom_files() -> Dict[str, dict]:
custom_files: Dict[str, dict] = {}
for option in settings['brand']['custom_files']:
filepath = settings['brand']['custom_files'][option]
if os.path.isfile(filepath):
custom_files.setdefault(option, {})
custom_files[option]['path'] = filepath
custom_files[option]['hash'] = get_hash_for_file(pathlib.Path(filepath))
if option == 'favicon_png':
custom_files[option]['mimetype'] = 'image/png'
if option == 'favicon_svg':
custom_files[option]['mimetype'] = 'image/svg+xml'
else:
logger.warning('%s does not exist or is not a file', filepath)
if 'favicon_svg' in custom_files and 'favicon_png' not in custom_files:
logger.warning('Some browsers only support PNG favicons, but you have only set an SVG favicon')
return custom_files
def get_result_templates(templates_path):
result_templates = set()
templates_path_length = len(templates_path) + 1

View file

@ -8,7 +8,6 @@ STATIC_BUILT_PATHS=(
'searx/static/themes/simple/js'
'searx/static/themes/simple/src/generated/pygments.less'
'searx/static/themes/simple/img'
'searx/templates/simple/searxng-wordmark.min.svg'
'searx/templates/simple/icons.html'
)