diff options
| author | Ivan Gabaldon <igabaldon@inetol.net> | 2025-12-02 10:18:00 +0000 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-12-02 10:18:00 +0000 |
| commit | fb089ae297b27f51777318e3a28bca8b172a4165 (patch) | |
| tree | 293e17a6ba3a7ae17c31bc6746794b97c012c6af /client/simple/src/js/core | |
| parent | ab8224c9394236d2cbcf6ec7d9bf0d7c602ca6ac (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.ts | 6 | ||||
| -rw-r--r-- | client/simple/src/js/core/listener.ts | 7 | ||||
| -rw-r--r-- | client/simple/src/js/core/nojs.ts | 8 | ||||
| -rw-r--r-- | client/simple/src/js/core/router.ts | 40 | ||||
| -rw-r--r-- | client/simple/src/js/core/toolkit.ts | 142 |
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(); |