summaryrefslogtreecommitdiff
path: root/client/simple/src/js/main/results.ts
diff options
context:
space:
mode:
authorIvan Gabaldon <igabaldon@inetol.net>2025-07-06 12:27:28 +0200
committerMarkus Heiser <markus.heiser@darmarIT.de>2025-08-18 16:38:32 +0200
commit60bd8b90f04d5d825fc8ac279cb7fdfde9fe78ea (patch)
tree19b2639638e7845597f9aa839eda39a456188a1c /client/simple/src/js/main/results.ts
parentadc4361eb919604889dc0661e75ef6ac8cfc4d23 (diff)
[enh] theme/simple: custom router
Lay the foundation for loading scripts granularly depending on the endpoint it's on. Remove vendor specific prefixes as there are now managed by browserslist and LightningCSS. Enabled quite a few rules in Biome that don't come in recommended to better catch issues and improve consistency. Related: - https://github.com/searxng/searxng/pull/5073#discussion_r2256037965 - https://github.com/searxng/searxng/pull/5073#discussion_r2256057100
Diffstat (limited to 'client/simple/src/js/main/results.ts')
-rw-r--r--client/simple/src/js/main/results.ts324
1 files changed, 159 insertions, 165 deletions
diff --git a/client/simple/src/js/main/results.ts b/client/simple/src/js/main/results.ts
index e278c894a..494f38cbc 100644
--- a/client/simple/src/js/main/results.ts
+++ b/client/simple/src/js/main/results.ts
@@ -1,181 +1,175 @@
import "../../../node_modules/swiped-events/src/swiped-events.js";
-import { assertElement, searxng } from "./00_toolkit.ts";
+import { assertElement, listen, mutable, settings } from "../core/toolkit.ts";
-const loadImage = (imgSrc: string, onSuccess: () => void): void => {
- // singleton image object, which is used for all loading processes of a detailed image
- const imgLoader = new Image();
+let imgTimeoutID: number;
- // set handlers in the on-properties
- imgLoader.onload = () => {
- onSuccess();
+const imageLoader = (resultElement: HTMLElement): void => {
+ if (imgTimeoutID) clearTimeout(imgTimeoutID);
+
+ const imgElement = resultElement.querySelector<HTMLImageElement>(".result-images-source img");
+ if (!imgElement) return;
+
+ // use thumbnail until full image loads
+ const thumbnail = resultElement.querySelector<HTMLImageElement>(".image_thumbnail");
+ if (thumbnail) {
+ if (thumbnail.src === `${settings.theme_static_path}/img/img_load_error.svg`) return;
+
+ imgElement.onerror = (): void => {
+ imgElement.src = thumbnail.src;
+ };
+
+ imgElement.src = thumbnail.src;
+ }
+
+ const imgSource = imgElement.getAttribute("data-src");
+ if (!imgSource) return;
+
+ // unsafe nodejs specific, cast to https://developer.mozilla.org/en-US/docs/Web/API/Window/setTimeout#return_value
+ // https://github.com/searxng/searxng/pull/5073#discussion_r2265767231
+ imgTimeoutID = setTimeout(() => {
+ imgElement.src = imgSource;
+ imgElement.removeAttribute("data-src");
+ }, 1000) as unknown as number;
+};
+
+const imageThumbnails: NodeListOf<HTMLImageElement> =
+ document.querySelectorAll<HTMLImageElement>("#urls img.image_thumbnail");
+for (const thumbnail of imageThumbnails) {
+ if (thumbnail.complete && thumbnail.naturalWidth === 0) {
+ thumbnail.src = `${settings.theme_static_path}/img/img_load_error.svg`;
+ }
+
+ thumbnail.onerror = (): void => {
+ thumbnail.src = `${settings.theme_static_path}/img/img_load_error.svg`;
};
+}
+
+const copyUrlButton: HTMLButtonElement | null =
+ document.querySelector<HTMLButtonElement>("#search_url button#copy_url");
+copyUrlButton?.style.setProperty("display", "block");
+
+mutable.selectImage = (resultElement: HTMLElement): void => {
+ // add a class that can be evaluated in the CSS and indicates that the
+ // detail view is open
+ const resultsElement = document.getElementById("results");
+ resultsElement?.classList.add("image-detail-open");
+
+ // add a hash to the browser history so that pressing back doesn't return
+ // to the previous page this allows us to dismiss the image details on
+ // pressing the back button on mobile devices
+ window.location.hash = "#image-viewer";
+
+ mutable.scrollPageToSelected?.();
- imgLoader.src = imgSrc;
+ // if there is no element given by the caller, stop here
+ if (!resultElement) return;
+
+ imageLoader(resultElement);
};
-searxng.ready(
- () => {
- const imageThumbnails = document.querySelectorAll<HTMLImageElement>("#urls img.image_thumbnail");
- for (const thumbnail of imageThumbnails) {
- if (thumbnail.complete && thumbnail.naturalWidth === 0) {
- thumbnail.src = `${searxng.settings.theme_static_path}/img/img_load_error.svg`;
- }
-
- thumbnail.onerror = () => {
- thumbnail.src = `${searxng.settings.theme_static_path}/img/img_load_error.svg`;
- };
- }
+mutable.closeDetail = (): void => {
+ const resultsElement = document.getElementById("results");
+ resultsElement?.classList.remove("image-detail-open");
- const copyUrlButton = document.querySelector<HTMLButtonElement>("#search_url button#copy_url");
- copyUrlButton?.style.setProperty("display", "block");
-
- searxng.listen("click", ".btn-collapse", function (this: HTMLElement) {
- const btnLabelCollapsed = this.getAttribute("data-btn-text-collapsed");
- const btnLabelNotCollapsed = this.getAttribute("data-btn-text-not-collapsed");
- const target = this.getAttribute("data-target");
-
- if (!target || !btnLabelCollapsed || !btnLabelNotCollapsed) return;
-
- const targetElement = document.querySelector<HTMLElement>(target);
- assertElement(targetElement);
-
- const isCollapsed = this.classList.contains("collapsed");
- const newLabel = isCollapsed ? btnLabelNotCollapsed : btnLabelCollapsed;
- const oldLabel = isCollapsed ? btnLabelCollapsed : btnLabelNotCollapsed;
-
- this.innerHTML = this.innerHTML.replace(oldLabel, newLabel);
- this.classList.toggle("collapsed");
-
- targetElement.classList.toggle("invisible");
- });
-
- searxng.listen("click", ".media-loader", function (this: HTMLElement) {
- const target = this.getAttribute("data-target");
- if (!target) return;
-
- const iframeLoad = document.querySelector<HTMLIFrameElement>(`${target} > iframe`);
- assertElement(iframeLoad);
-
- const srctest = iframeLoad.getAttribute("src");
- if (!srctest) {
- const dataSrc = iframeLoad.getAttribute("data-src");
- if (dataSrc) {
- iframeLoad.setAttribute("src", dataSrc);
- }
- }
- });
-
- searxng.listen("click", "#copy_url", async function (this: HTMLElement) {
- const target = this.parentElement?.querySelector<HTMLPreElement>("pre");
- assertElement(target);
-
- await navigator.clipboard.writeText(target.innerText);
- const copiedText = this.dataset.copiedText;
- if (copiedText) {
- this.innerText = copiedText;
- }
- });
-
- searxng.selectImage = (resultElement: Element): void => {
- // add a class that can be evaluated in the CSS and indicates that the
- // detail view is open
- const resultsElement = document.getElementById("results");
- resultsElement?.classList.add("image-detail-open");
-
- // add a hash to the browser history so that pressing back doesn't return
- // to the previous page this allows us to dismiss the image details on
- // pressing the back button on mobile devices
- window.location.hash = "#image-viewer";
-
- searxng.scrollPageToSelected?.();
-
- // if there is no element given by the caller, stop here
- if (!resultElement) return;
-
- // find image element, if there is none, stop here
- const img = resultElement.querySelector<HTMLImageElement>(".result-images-source img");
- if (!img) return;
-
- // <img src="" data-src="http://example.org/image.jpg">
- const src = img.getAttribute("data-src");
- if (!src) return;
-
- // use thumbnail until full image loads
- const thumbnail = resultElement.querySelector<HTMLImageElement>(".image_thumbnail");
- if (thumbnail) {
- img.src = thumbnail.src;
- }
-
- // load full size image
- loadImage(src, () => {
- img.src = src;
- img.onerror = () => {
- img.src = `${searxng.settings.theme_static_path}/img/img_load_error.svg`;
- };
-
- img.removeAttribute("data-src");
- });
- };
+ // remove #image-viewer hash from url by navigating back
+ if (window.location.hash === "#image-viewer") {
+ window.history.back();
+ }
- searxng.closeDetail = (): void => {
- const resultsElement = document.getElementById("results");
- resultsElement?.classList.remove("image-detail-open");
+ mutable.scrollPageToSelected?.();
+};
- // remove #image-viewer hash from url by navigating back
- if (window.location.hash === "#image-viewer") {
- window.history.back();
- }
+listen("click", ".btn-collapse", function (this: HTMLElement) {
+ const btnLabelCollapsed = this.getAttribute("data-btn-text-collapsed");
+ const btnLabelNotCollapsed = this.getAttribute("data-btn-text-not-collapsed");
+ const target = this.getAttribute("data-target");
- searxng.scrollPageToSelected?.();
- };
+ if (!(target && btnLabelCollapsed && btnLabelNotCollapsed)) return;
+
+ const targetElement = document.querySelector<HTMLElement>(target);
+ assertElement(targetElement);
- searxng.listen("click", ".result-detail-close", (event: Event) => {
- event.preventDefault();
- searxng.closeDetail?.();
- });
-
- searxng.listen("click", ".result-detail-previous", (event: Event) => {
- event.preventDefault();
- searxng.selectPrevious?.(false);
- });
-
- searxng.listen("click", ".result-detail-next", (event: Event) => {
- event.preventDefault();
- searxng.selectNext?.(false);
- });
-
- // listen for the back button to be pressed and dismiss the image details when called
- window.addEventListener("hashchange", () => {
- if (window.location.hash !== "#image-viewer") {
- searxng.closeDetail?.();
- }
- });
-
- const swipeHorizontal = document.querySelectorAll<HTMLElement>(".swipe-horizontal");
- for (const element of swipeHorizontal) {
- searxng.listen("swiped-left", element, () => {
- searxng.selectNext?.(false);
- });
-
- searxng.listen("swiped-right", element, () => {
- searxng.selectPrevious?.(false);
- });
+ const isCollapsed = this.classList.contains("collapsed");
+ const newLabel = isCollapsed ? btnLabelNotCollapsed : btnLabelCollapsed;
+ const oldLabel = isCollapsed ? btnLabelCollapsed : btnLabelNotCollapsed;
+
+ this.innerHTML = this.innerHTML.replace(oldLabel, newLabel);
+ this.classList.toggle("collapsed");
+
+ targetElement.classList.toggle("invisible");
+});
+
+listen("click", ".media-loader", function (this: HTMLElement) {
+ const target = this.getAttribute("data-target");
+ if (!target) return;
+
+ const iframeLoad = document.querySelector<HTMLIFrameElement>(`${target} > iframe`);
+ assertElement(iframeLoad);
+
+ const srctest = iframeLoad.getAttribute("src");
+ if (!srctest) {
+ const dataSrc = iframeLoad.getAttribute("data-src");
+ if (dataSrc) {
+ iframeLoad.setAttribute("src", dataSrc);
}
+ }
+});
+
+listen("click", "#copy_url", async function (this: HTMLElement) {
+ const target = this.parentElement?.querySelector<HTMLPreElement>("pre");
+ assertElement(target);
+
+ await navigator.clipboard.writeText(target.innerText);
+ const copiedText = this.dataset.copiedText;
+ if (copiedText) {
+ this.innerText = copiedText;
+ }
+});
+
+listen("click", ".result-detail-close", (event: Event) => {
+ event.preventDefault();
+ mutable.closeDetail?.();
+});
+
+listen("click", ".result-detail-previous", (event: Event) => {
+ event.preventDefault();
+ mutable.selectPrevious?.(false);
+});
+
+listen("click", ".result-detail-next", (event: Event) => {
+ event.preventDefault();
+ mutable.selectNext?.(false);
+});
+
+// listen for the back button to be pressed and dismiss the image details when called
+window.addEventListener("hashchange", () => {
+ if (window.location.hash !== "#image-viewer") {
+ mutable.closeDetail?.();
+ }
+});
+
+const swipeHorizontal: NodeListOf<HTMLElement> = document.querySelectorAll<HTMLElement>(".swipe-horizontal");
+for (const element of swipeHorizontal) {
+ listen("swiped-left", element, () => {
+ mutable.selectNext?.(false);
+ });
+
+ listen("swiped-right", element, () => {
+ mutable.selectPrevious?.(false);
+ });
+}
+
+window.addEventListener(
+ "scroll",
+ () => {
+ const backToTopElement = document.getElementById("backToTop");
+ const resultsElement = document.getElementById("results");
- window.addEventListener(
- "scroll",
- () => {
- const backToTopElement = document.getElementById("backToTop");
- const resultsElement = document.getElementById("results");
-
- if (backToTopElement && resultsElement) {
- const scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
- const isScrolling = scrollTop >= 100;
- resultsElement.classList.toggle("scrolling", isScrolling);
- }
- },
- true
- );
+ if (backToTopElement && resultsElement) {
+ const scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
+ const isScrolling = scrollTop >= 100;
+ resultsElement.classList.toggle("scrolling", isScrolling);
+ }
},
- { on: [searxng.endpoint === "results"] }
+ true
);