[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.
This commit is contained in:
vojkovic
2026-09-20 10:00:04 +00:00
committed by Brock Vojkovic
parent e75fd22d09
commit bcd2b709fe
5 changed files with 81 additions and 33 deletions

View File

@@ -5,6 +5,7 @@ import { listen, mutable, settings } from "../toolkit.ts";
import { assertElement } from "../util/assertElement.ts"; import { assertElement } from "../util/assertElement.ts";
let imgTimeoutID: number; let imgTimeoutID: number;
const loadErrorSrc = `${settings.theme_static_path}/img/img_load_error.svg`;
const imageLoader = (resultElement: HTMLElement): void => { const imageLoader = (resultElement: HTMLElement): void => {
if (imgTimeoutID) clearTimeout(imgTimeoutID); if (imgTimeoutID) clearTimeout(imgTimeoutID);
@@ -15,7 +16,7 @@ const imageLoader = (resultElement: HTMLElement): void => {
// use thumbnail until full image loads // use thumbnail until full image loads
const thumbnail = resultElement.querySelector<HTMLImageElement>(".image_thumbnail"); const thumbnail = resultElement.querySelector<HTMLImageElement>(".image_thumbnail");
if (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.onerror = (): void => {
imgElement.src = thumbnail.src; imgElement.src = thumbnail.src;
@@ -35,17 +36,27 @@ const imageLoader = (resultElement: HTMLElement): void => {
}, 1000) as unknown as number; }, 1000) as unknown as number;
}; };
const imageThumbnails: NodeListOf<HTMLImageElement> = document.querySelectorAll<HTMLImageElement>( const hideBrokenThumbnail = (img: HTMLImageElement): void => {
"#urls img.image_thumbnail, img.thumbnail" if (img.classList.contains("thumbnail")) {
); img.closest("a.thumbnail_link")?.remove();
for (const thumbnail of imageThumbnails) { return;
if (thumbnail.complete && thumbnail.naturalWidth === 0) {
thumbnail.src = `${settings.theme_static_path}/img/img_load_error.svg`;
} }
if (img.classList.contains("image_thumbnail") && img.src !== loadErrorSrc) {
img.src = loadErrorSrc;
}
};
thumbnail.onerror = (): void => { document.addEventListener(
thumbnail.src = `${settings.theme_static_path}/img/img_load_error.svg`; "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<HTMLImageElement>("img.thumbnail, #urls img.image_thumbnail")) {
if (img.complete && img.naturalWidth === 0) hideBrokenThumbnail(img);
} }
const copyUrlButton: HTMLButtonElement | null = const copyUrlButton: HTMLButtonElement | null =

View File

@@ -255,6 +255,8 @@ article[data-vim-selected].category-social {
font-size: 1rem; font-size: 1rem;
color: var(--color-result-url-font); color: var(--color-result-url-font);
flex-flow: row nowrap; flex-flow: row nowrap;
flex: 1;
min-width: 0;
overflow: hidden; overflow: hidden;
margin: 0; margin: 0;
padding: 0; padding: 0;
@@ -305,22 +307,44 @@ article[data-vim-selected].category-social {
a.thumbnail_link { a.thumbnail_link {
position: relative; position: relative;
margin-top: 0.6rem; display: block;
.ltr-margin-right(1rem); .ltr-float-right();
.ltr-float-left(); .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 { img.thumbnail {
width: 7rem; position: relative;
height: unset; // remove height value that was needed for lazy loading width: 100%;
height: 100%;
object-fit: contain;
display: block; display: block;
} }
.thumbnail_length { .thumbnail_length {
.image-label-bottom-right(); .image-label-bottom-right();
right: 6px; right: 6px;
z-index: 1;
} }
} }
.result_inner {
overflow: hidden;
}
.break { .break {
clear: both; clear: both;
} }
@@ -408,8 +432,12 @@ article[data-vim-selected].category-social {
} }
.result-videos { .result-videos {
a.thumbnail_link img.thumbnail { a.thumbnail_link {
width: 20rem; .ltr-float-left();
.ltr-margin-right(1rem);
.ltr-margin-left(0);
width: 10rem !important;
aspect-ratio: 16 / 9 !important;
} }
.content { .content {
@@ -433,6 +461,8 @@ article[data-vim-selected].category-social {
.engines { .engines {
.ltr-float-right(); .ltr-float-right();
clear: both;
margin-top: 0.5rem;
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
justify-content: flex-end; justify-content: flex-end;
@@ -930,10 +960,6 @@ summary.title {
} }
.result { .result {
.thumbnail {
max-width: 98%;
}
.url { .url {
span.url { span.url {
display: block; display: block;
@@ -1144,13 +1170,11 @@ summary.title {
*/ */
@media screen and (max-width: @small-phone) { @media screen and (max-width: @small-phone) {
.result-videos { .result {
img.thumbnail { a.thumbnail_link {
float: none !important; aspect-ratio: 1 / 1;
} width: 7rem;
border-radius: 1.25rem;
.content {
overflow: inherit;
} }
} }
} }

View File

@@ -17,9 +17,21 @@
{{ result_open_link(url, classes) }}{{ title }}{{ result_close_link() }} {{ result_open_link(url, classes) }}{{ title }}{{ result_close_link() }}
{%- endmacro -%} {%- endmacro -%}
<!-- Draw result thumbnail -->
{% macro result_thumbnail(result, image_proxify) -%}
{%- if result.thumbnail -%}
{%- set thumb_src = image_proxify(result.thumbnail) -%}
{{ result_open_link(result.url, classes='thumbnail_link') -}}
<img class="thumbnail_bg" src="{{ thumb_src }}" alt="" aria-hidden="true"><img class="thumbnail" src="{{ thumb_src }}" alt="" loading="lazy">{%- if result.length -%}<span class="thumbnail_length">{{ result.length }}</span>{%- endif -%}
{{- result_close_link() -}}
{%- endif -%}
{%- endmacro -%}
<!-- Draw result header --> <!-- Draw result header -->
{% macro result_header(result, favicons, image_proxify) -%} {% macro result_header(result, favicons, image_proxify, thumbnail=True) -%}
<article class="result {% if result['template'] %}result-{{ result.template|replace('.html', '') }}{% else %}result-default{% endif %} {% if result['category'] %}category-{{ result['category'] }}{% endif %}"> <article class="result {% if result['template'] %}result-{{ result.template|replace('.html', '') }}{% else %}result-default{% endif %} {% if result['category'] %}category-{{ result['category'] }}{% endif %}">
{%- if thumbnail %}{{ result_thumbnail(result, image_proxify) }}{% endif -%}
<div class="result_inner">
{{- result_open_link(result.url, "url_header") -}} {{- result_open_link(result.url, "url_header") -}}
{%- if favicon_resolver != "" %} {%- if favicon_resolver != "" %}
<div class="favicon"><img loading="lazy" src="{{ favicon_url(result.parsed_url.netloc) }}"></div> <div class="favicon"><img loading="lazy" src="{{ favicon_url(result.parsed_url.netloc) }}"></div>
@@ -30,7 +42,6 @@
{%- endfor %} {%- endfor %}
</div> </div>
{{- result_close_link() -}} {{- result_close_link() -}}
{%- if result.thumbnail %}{{ result_open_link(result.url, classes='thumbnail_link') }}<img class="thumbnail" src="{{ image_proxify(result.thumbnail) }}" title="{{ result.title|striptags }}" loading="lazy">{%- if result.length -%}<span class="thumbnail_length">{{ result.length }}</span>{%- endif -%}{{ result_close_link() }}{% endif -%}
<h3>{{ result_link(result.url, result.title|safe) }}</h3> <h3>{{ result_link(result.url, result.title|safe) }}</h3>
{%- endmacro -%} {%- endmacro -%}
@@ -46,6 +57,7 @@
<!-- Draw result sub footer --> <!-- Draw result sub footer -->
{%- macro result_sub_footer(result) -%} {%- macro result_sub_footer(result) -%}
</div>
<div class="engines"> <div class="engines">
{% for engine in result.engines %}<span>{{ engine }}</span>{% endfor %} {% for engine in result.engines %}<span>{{ engine }}</span>{% endfor %}
{{ icon_small('ellipsis-vertical') + result_link(cache_url + result.url, _('cached'), "cache_link") }} {{ icon_small('ellipsis-vertical') + result_link(cache_url + result.url, _('cached'), "cache_link") }}

View File

@@ -70,6 +70,7 @@
</div> </div>
{%- endif -%} {%- endif -%}
</div>{{- '' -}} </div>{{- '' -}}
</div>
<div class="break"></div> <div class="break"></div>
{{- result_footer(result) }} {{- result_footer(result) }}

View File

@@ -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) }} {{ result_sub_header(result) }}
{% if result.iframe_src -%} {% if result.iframe_src -%}
<p class="altlink"> <a class="btn-collapse collapsed media-loader disabled_if_nojs" data-target="#result-video-{{ index }}" data-btn-text-collapsed="{{ _('show video') }}" data-btn-text-not-collapsed="{{ _('hide video') }}">{{ icon_small('film') }} {{ _('show video') }}</a></p> <p class="altlink"> <a class="btn-collapse collapsed media-loader disabled_if_nojs" data-target="#result-video-{{ index }}" data-btn-text-collapsed="{{ _('show video') }}" data-btn-text-not-collapsed="{{ _('hide video') }}">{{ icon_small('film') }} {{ _('show video') }}</a></p>
{%- endif %} {%- endif %}
{{ result_thumbnail(result, image_proxify) }}
{%- if result.content %} {%- if result.content %}
<p class="content"> <p class="content">
{{ result.content|safe }} {{ result.content|safe }}
@@ -14,7 +15,6 @@
{{ _('This site did not provide any description.')|safe }} {{ _('This site did not provide any description.')|safe }}
</p> </p>
{% endif -%} {% endif -%}
</p>
{{- result_sub_footer(result) -}} {{- result_sub_footer(result) -}}
{% if result.iframe_src -%} {% if result.iframe_src -%}
<div id="result-video-{{ index }}" class="embedded-video invisible"> <div id="result-video-{{ index }}" class="embedded-video invisible">