summaryrefslogtreecommitdiff
path: root/client/simple/src/js/core
diff options
context:
space:
mode:
authorIvan Gabaldon <igabaldon@inetol.net>2025-12-02 10:18:00 +0000
committerGitHub <noreply@github.com>2025-12-02 10:18:00 +0000
commitfb089ae297b27f51777318e3a28bca8b172a4165 (patch)
tree293e17a6ba3a7ae17c31bc6746794b97c012c6af /client/simple/src/js/core
parentab8224c9394236d2cbcf6ec7d9bf0d7c602ca6ac (diff)
[mod] client/simple: client plugins (#5406)
* [mod] client/simple: client plugins Defines a new interface for client side *"plugins"* that coexist with server side plugin system. Each plugin (e.g., `InfiniteScroll`) extends the base `ts Plugin`. Client side plugins are independent and lazy‑loaded via `router.ts` when their `load()` conditions are met. On each navigation request, all applicable plugins are instanced. Since these are client side plugins, we can only invoke them once DOM is fully loaded. E.g. `Calculator` will not render a new `answer` block until fully loaded and executed. For some plugins, we might want to handle its availability in `settings.yml` and toggle in UI, like we do for server side plugins. In that case, we extend `py Plugin` instancing only the information and then checking client side if [`settings.plugins`](https://github.com/inetol/searxng/blob/1ad832b1dc33f3f388da361ff2459b05dc86a164/client/simple/src/js/toolkit.ts#L134) array has the plugin id. * [mod] client/simple: rebuild static
Diffstat (limited to 'client/simple/src/js/core')
-rw-r--r--client/simple/src/js/core/index.ts6
-rw-r--r--client/simple/src/js/core/listener.ts7
-rw-r--r--client/simple/src/js/core/nojs.ts8
-rw-r--r--client/simple/src/js/core/router.ts40
-rw-r--r--client/simple/src/js/core/toolkit.ts142
5 files changed, 0 insertions, 203 deletions
diff --git a/client/simple/src/js/core/index.ts b/client/simple/src/js/core/index.ts
deleted file mode 100644
index 59e64182c..000000000
--- a/client/simple/src/js/core/index.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-// SPDX-License-Identifier: AGPL-3.0-or-later
-
-import "./nojs.ts";
-import "./router.ts";
-import "./toolkit.ts";
-import "./listener.ts";
diff --git a/client/simple/src/js/core/listener.ts b/client/simple/src/js/core/listener.ts
deleted file mode 100644
index b8c0cbfd5..000000000
--- a/client/simple/src/js/core/listener.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-// SPDX-License-Identifier: AGPL-3.0-or-later
-
-import { listen } from "./toolkit.ts";
-
-listen("click", ".close", function (this: HTMLElement) {
- (this.parentNode as HTMLElement)?.classList.add("invisible");
-});
diff --git a/client/simple/src/js/core/nojs.ts b/client/simple/src/js/core/nojs.ts
deleted file mode 100644
index 65c62dd90..000000000
--- a/client/simple/src/js/core/nojs.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-// SPDX-License-Identifier: AGPL-3.0-or-later
-
-import { ready } from "./toolkit.ts";
-
-ready(() => {
- document.documentElement.classList.remove("no-js");
- document.documentElement.classList.add("js");
-});
diff --git a/client/simple/src/js/core/router.ts b/client/simple/src/js/core/router.ts
deleted file mode 100644
index bea838713..000000000
--- a/client/simple/src/js/core/router.ts
+++ /dev/null
@@ -1,40 +0,0 @@
-// SPDX-License-Identifier: AGPL-3.0-or-later
-
-import { Endpoints, endpoint, ready, settings } from "./toolkit.ts";
-
-ready(
- () => {
- void import("../main/keyboard.ts");
- void import("../main/search.ts");
-
- if (settings.autocomplete) {
- void import("../main/autocomplete.ts");
- }
- },
- { on: [endpoint === Endpoints.index] }
-);
-
-ready(
- () => {
- void import("../main/keyboard.ts");
- void import("../main/mapresult.ts");
- void import("../main/results.ts");
- void import("../main/search.ts");
-
- if (settings.infinite_scroll) {
- void import("../main/infinite_scroll.ts");
- }
-
- if (settings.autocomplete) {
- void import("../main/autocomplete.ts");
- }
- },
- { on: [endpoint === Endpoints.results] }
-);
-
-ready(
- () => {
- void import("../main/preferences.ts");
- },
- { on: [endpoint === Endpoints.preferences] }
-);
diff --git a/client/simple/src/js/core/toolkit.ts b/client/simple/src/js/core/toolkit.ts
deleted file mode 100644
index d80167aa5..000000000
--- a/client/simple/src/js/core/toolkit.ts
+++ /dev/null
@@ -1,142 +0,0 @@
-// SPDX-License-Identifier: AGPL-3.0-or-later
-
-import type { KeyBindingLayout } from "../main/keyboard.ts";
-
-// synced with searx/webapp.py get_client_settings
-type Settings = {
- advanced_search?: boolean;
- autocomplete?: string;
- autocomplete_min?: number;
- doi_resolver?: string;
- favicon_resolver?: string;
- hotkeys?: KeyBindingLayout;
- infinite_scroll?: boolean;
- method?: "GET" | "POST";
- query_in_title?: boolean;
- results_on_new_tab?: boolean;
- safesearch?: 0 | 1 | 2;
- search_on_category_select?: boolean;
- theme?: string;
- theme_static_path?: string;
- translations?: Record<string, string>;
- url_formatting?: "pretty" | "full" | "host";
-};
-
-type HTTPOptions = {
- body?: BodyInit;
- timeout?: number;
-};
-
-type ReadyOptions = {
- // all values must be truthy for the callback to be executed
- on?: (boolean | undefined)[];
-};
-
-type AssertElement = (element?: HTMLElement | null) => asserts element is HTMLElement;
-
-export type EndpointsKeys = keyof typeof Endpoints;
-
-export const Endpoints = {
- index: "index",
- results: "results",
- preferences: "preferences",
- unknown: "unknown"
-} as const;
-
-export const mutable = {
- closeDetail: undefined as (() => void) | undefined,
- scrollPageToSelected: undefined as (() => void) | undefined,
- selectImage: undefined as ((resultElement: HTMLElement) => void) | undefined,
- selectNext: undefined as ((openDetailView?: boolean) => void) | undefined,
- selectPrevious: undefined as ((openDetailView?: boolean) => void) | undefined
-};
-
-const getEndpoint = (): EndpointsKeys => {
- const metaEndpoint = document.querySelector('meta[name="endpoint"]')?.getAttribute("content");
-
- if (metaEndpoint && metaEndpoint in Endpoints) {
- return metaEndpoint as EndpointsKeys;
- }
-
- return Endpoints.unknown;
-};
-
-const getSettings = (): Settings => {
- const settings = document.querySelector("script[client_settings]")?.getAttribute("client_settings");
- if (!settings) return {};
-
- try {
- return JSON.parse(atob(settings));
- } catch (error) {
- console.error("Failed to load client_settings:", error);
- return {};
- }
-};
-
-export const assertElement: AssertElement = (element?: HTMLElement | null): asserts element is HTMLElement => {
- if (!element) {
- throw new Error("Bad assertion: DOM element not found");
- }
-};
-
-export const http = async (method: string, url: string | URL, options?: HTTPOptions): Promise<Response> => {
- const controller = new AbortController();
- const timeoutId = setTimeout(() => controller.abort(), options?.timeout ?? 30_000);
-
- const res = await fetch(url, {
- body: options?.body,
- method: method,
- signal: controller.signal
- }).finally(() => clearTimeout(timeoutId));
- if (!res.ok) {
- throw new Error(res.statusText);
- }
-
- return res;
-};
-
-export const listen = <K extends keyof DocumentEventMap, E extends HTMLElement>(
- type: string | K,
- target: string | Document | E,
- listener: (this: E, event: DocumentEventMap[K]) => void | Promise<void>,
- options?: AddEventListenerOptions
-): void => {
- if (typeof target !== "string") {
- target.addEventListener(type, listener as EventListener, options);
- return;
- }
-
- document.addEventListener(
- type,
- (event: Event) => {
- for (const node of event.composedPath()) {
- if (node instanceof HTMLElement && node.matches(target)) {
- try {
- listener.call(node as E, event as DocumentEventMap[K]);
- } catch (error) {
- console.error(error);
- }
- break;
- }
- }
- },
- options
- );
-};
-
-export const ready = (callback: () => void, options?: ReadyOptions): void => {
- for (const condition of options?.on ?? []) {
- if (!condition) {
- return;
- }
- }
-
- if (document.readyState !== "loading") {
- callback();
- } else {
- listen("DOMContentLoaded", document, callback, { once: true });
- }
-};
-
-export const endpoint: EndpointsKeys = getEndpoint();
-export const settings: Settings = getSettings();