From bcd2b709fe93e0ccf4d15bd55f7ac8d4c75e8ca0 Mon Sep 17 00:00:00 2001
From: vojkovic
Date: Sun, 20 Sep 2026 10:00:04 +0000
Subject: [PATCH] [mod] theme: improve thumbnails display on results page
Deletes thumbnails instead of showing an error image and also moves them to the right side. Also gives them a background and makes them into rounded 4:3 rectangles for better ui.
---
client/simple/src/js/main/results.ts | 31 ++++++----
client/simple/src/less/style.less | 60 +++++++++++++------
searx/templates/simple/macros.html | 16 ++++-
.../simple/result_templates/packages.html | 1 +
.../simple/result_templates/videos.html | 6 +-
5 files changed, 81 insertions(+), 33 deletions(-)
diff --git a/client/simple/src/js/main/results.ts b/client/simple/src/js/main/results.ts
index 29cab399a..d3b22955c 100644
--- a/client/simple/src/js/main/results.ts
+++ b/client/simple/src/js/main/results.ts
@@ -5,6 +5,7 @@ import { listen, mutable, settings } from "../toolkit.ts";
import { assertElement } from "../util/assertElement.ts";
let imgTimeoutID: number;
+const loadErrorSrc = `${settings.theme_static_path}/img/img_load_error.svg`;
const imageLoader = (resultElement: HTMLElement): void => {
if (imgTimeoutID) clearTimeout(imgTimeoutID);
@@ -15,7 +16,7 @@ const imageLoader = (resultElement: HTMLElement): void => {
// use thumbnail until full image loads
const thumbnail = resultElement.querySelector(".image_thumbnail");
if (thumbnail) {
- if (thumbnail.src === `${settings.theme_static_path}/img/img_load_error.svg`) return;
+ if (thumbnail.src === loadErrorSrc) return;
imgElement.onerror = (): void => {
imgElement.src = thumbnail.src;
@@ -35,17 +36,27 @@ const imageLoader = (resultElement: HTMLElement): void => {
}, 1000) as unknown as number;
};
-const imageThumbnails: NodeListOf = document.querySelectorAll(
- "#urls img.image_thumbnail, img.thumbnail"
-);
-for (const thumbnail of imageThumbnails) {
- if (thumbnail.complete && thumbnail.naturalWidth === 0) {
- thumbnail.src = `${settings.theme_static_path}/img/img_load_error.svg`;
+const hideBrokenThumbnail = (img: HTMLImageElement): void => {
+ if (img.classList.contains("thumbnail")) {
+ img.closest("a.thumbnail_link")?.remove();
+ return;
}
+ if (img.classList.contains("image_thumbnail") && img.src !== loadErrorSrc) {
+ img.src = loadErrorSrc;
+ }
+};
- thumbnail.onerror = (): void => {
- thumbnail.src = `${settings.theme_static_path}/img/img_load_error.svg`;
- };
+document.addEventListener(
+ "error",
+ (event: Event) => {
+ const img = event.target;
+ if (img instanceof HTMLImageElement) hideBrokenThumbnail(img);
+ },
+ true
+);
+// some images may have already failed
+for (const img of document.querySelectorAll("img.thumbnail, #urls img.image_thumbnail")) {
+ if (img.complete && img.naturalWidth === 0) hideBrokenThumbnail(img);
}
const copyUrlButton: HTMLButtonElement | null =
diff --git a/client/simple/src/less/style.less b/client/simple/src/less/style.less
index 46ba5d491..3bb13e80f 100644
--- a/client/simple/src/less/style.less
+++ b/client/simple/src/less/style.less
@@ -255,6 +255,8 @@ article[data-vim-selected].category-social {
font-size: 1rem;
color: var(--color-result-url-font);
flex-flow: row nowrap;
+ flex: 1;
+ min-width: 0;
overflow: hidden;
margin: 0;
padding: 0;
@@ -305,22 +307,44 @@ article[data-vim-selected].category-social {
a.thumbnail_link {
position: relative;
- margin-top: 0.6rem;
- .ltr-margin-right(1rem);
- .ltr-float-left();
+ display: block;
+ .ltr-float-right();
+ .ltr-margin-left(0.5rem);
+ width: 8rem;
+ aspect-ratio: 4 / 3;
+ border-radius: 0.5rem;
+ overflow: hidden;
+ background: var(--color-result-border);
+
+ img.thumbnail_bg {
+ position: absolute;
+ inset: 0;
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+ filter: blur(0.25rem);
+ pointer-events: none;
+ }
img.thumbnail {
- width: 7rem;
- height: unset; // remove height value that was needed for lazy loading
+ position: relative;
+ width: 100%;
+ height: 100%;
+ object-fit: contain;
display: block;
}
.thumbnail_length {
.image-label-bottom-right();
right: 6px;
+ z-index: 1;
}
}
+ .result_inner {
+ overflow: hidden;
+ }
+
.break {
clear: both;
}
@@ -408,8 +432,12 @@ article[data-vim-selected].category-social {
}
.result-videos {
- a.thumbnail_link img.thumbnail {
- width: 20rem;
+ a.thumbnail_link {
+ .ltr-float-left();
+ .ltr-margin-right(1rem);
+ .ltr-margin-left(0);
+ width: 10rem !important;
+ aspect-ratio: 16 / 9 !important;
}
.content {
@@ -433,6 +461,8 @@ article[data-vim-selected].category-social {
.engines {
.ltr-float-right();
+ clear: both;
+ margin-top: 0.5rem;
display: flex;
flex-wrap: wrap;
justify-content: flex-end;
@@ -930,10 +960,6 @@ summary.title {
}
.result {
- .thumbnail {
- max-width: 98%;
- }
-
.url {
span.url {
display: block;
@@ -1144,13 +1170,11 @@ summary.title {
*/
@media screen and (max-width: @small-phone) {
- .result-videos {
- img.thumbnail {
- float: none !important;
- }
-
- .content {
- overflow: inherit;
+ .result {
+ a.thumbnail_link {
+ aspect-ratio: 1 / 1;
+ width: 7rem;
+ border-radius: 1.25rem;
}
}
}
diff --git a/searx/templates/simple/macros.html b/searx/templates/simple/macros.html
index 8941b6820..4e9ce324b 100644
--- a/searx/templates/simple/macros.html
+++ b/searx/templates/simple/macros.html
@@ -17,9 +17,21 @@
{{ result_open_link(url, classes) }}{{ title }}{{ result_close_link() }}
{%- endmacro -%}
+
+{% macro result_thumbnail(result, image_proxify) -%}
+ {%- if result.thumbnail -%}
+ {%- set thumb_src = image_proxify(result.thumbnail) -%}
+ {{ result_open_link(result.url, classes='thumbnail_link') -}}
+ 
{%- if result.length -%}{{ result.length }}{%- endif -%}
+ {{- result_close_link() -}}
+ {%- endif -%}
+{%- endmacro -%}
+
-{% macro result_header(result, favicons, image_proxify) -%}
+{% macro result_header(result, favicons, image_proxify, thumbnail=True) -%}
+ {%- if thumbnail %}{{ result_thumbnail(result, image_proxify) }}{% endif -%}
+
{{- result_open_link(result.url, "url_header") -}}
{%- if favicon_resolver != "" %}
@@ -30,7 +42,6 @@
{%- endfor %}
{{- result_close_link() -}}
- {%- if result.thumbnail %}{{ result_open_link(result.url, classes='thumbnail_link') }}
{%- if result.length -%}{{ result.length }}{%- endif -%}{{ result_close_link() }}{% endif -%}
{{ result_link(result.url, result.title|safe) }}
{%- endmacro -%}
@@ -46,6 +57,7 @@
{%- macro result_sub_footer(result) -%}
+
{% for engine in result.engines %}{{ engine }}{% endfor %}
{{ icon_small('ellipsis-vertical') + result_link(cache_url + result.url, _('cached'), "cache_link") }}
diff --git a/searx/templates/simple/result_templates/packages.html b/searx/templates/simple/result_templates/packages.html
index 8608621ba..12acbe39f 100644
--- a/searx/templates/simple/result_templates/packages.html
+++ b/searx/templates/simple/result_templates/packages.html
@@ -70,6 +70,7 @@
{%- endif -%}
{{- '' -}}
+
{{- result_footer(result) }}
diff --git a/searx/templates/simple/result_templates/videos.html b/searx/templates/simple/result_templates/videos.html
index a336c25ed..7e32306b1 100644
--- a/searx/templates/simple/result_templates/videos.html
+++ b/searx/templates/simple/result_templates/videos.html
@@ -1,10 +1,11 @@
-{% from 'simple/macros.html' import iframe, result_header, result_sub_header, result_sub_footer, result_footer with context %}
+{% from 'simple/macros.html' import iframe, result_header, result_sub_header, result_sub_footer, result_footer, result_thumbnail with context %}
-{{ result_header(result, favicons, image_proxify) }}
+{{ result_header(result, favicons, image_proxify, thumbnail=False) }}
{{ result_sub_header(result) }}
{% if result.iframe_src -%}
{{ icon_small('film') }} {{ _('show video') }}
{%- endif %}
+{{ result_thumbnail(result, image_proxify) }}
{%- if result.content %}
{{ result.content|safe }}
@@ -14,7 +15,6 @@
{{ _('This site did not provide any description.')|safe }}
{% endif -%}
-
{{- result_sub_footer(result) -}}
{% if result.iframe_src -%}