Files
searxng/client/simple/src/js/toolkit.ts
T
Markus Heiser 4ac822fd7f [mod] typification of the preference settings
no functional change / except the missing online doc which is now available::

    $ make docs.live
    $ xdg-open "http://127.0.0.1:8000/admin/settings/settings_preferences.html"

Signed-off-by: Markus Heiser <markus.heiser@darmarit.de>
2026-05-30 22:43:50 +08:00

134 lines
3.6 KiB
TypeScript

// 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 = {
plugins?: string[];
autocomplete?: string;
autocomplete_min?: number;
doi_resolver?: string;
favicon_resolver?: string;
hotkeys?: KeyBindingLayout;
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)[];
};
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 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") {
listen("DOMContentLoaded", document, callback, { once: true });
} else {
callback();
}
};
export const endpoint: EndpointsKeys = getEndpoint();
export const settings: Settings = getSettings();