mirror of
https://github.com/searxng/searxng.git
synced 2026-01-01 00:20:02 +00:00
[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
This commit is contained in:
committed by
Markus Heiser
parent
adc4361eb9
commit
60bd8b90f0
@@ -1,4 +1,4 @@
|
||||
import { assertElement, searxng } from "./00_toolkit.ts";
|
||||
import { assertElement, listen, settings } from "../core/toolkit.ts";
|
||||
|
||||
const submitIfQuery = (qInput: HTMLInputElement): void => {
|
||||
if (qInput.value.length > 0) {
|
||||
@@ -17,217 +17,88 @@ const createClearButton = (qInput: HTMLInputElement): void => {
|
||||
|
||||
updateClearButton(qInput, cs);
|
||||
|
||||
searxng.listen("click", cs, (event: MouseEvent) => {
|
||||
listen("click", cs, (event: MouseEvent) => {
|
||||
event.preventDefault();
|
||||
qInput.value = "";
|
||||
qInput.focus();
|
||||
updateClearButton(qInput, cs);
|
||||
});
|
||||
|
||||
searxng.listen("input", qInput, () => updateClearButton(qInput, cs), { passive: true });
|
||||
listen("input", qInput, () => updateClearButton(qInput, cs), { passive: true });
|
||||
};
|
||||
|
||||
const fetchResults = async (qInput: HTMLInputElement, query: string): Promise<void> => {
|
||||
try {
|
||||
let res: Response;
|
||||
const qInput = document.getElementById("q") as HTMLInputElement | null;
|
||||
assertElement(qInput);
|
||||
|
||||
if (searxng.settings.method === "GET") {
|
||||
res = await searxng.http("GET", `./autocompleter?q=${query}`);
|
||||
} else {
|
||||
res = await searxng.http("POST", "./autocompleter", new URLSearchParams({ q: query }));
|
||||
}
|
||||
const isMobile: boolean = window.matchMedia("(max-width: 50em)").matches;
|
||||
const isResultsPage: boolean = document.querySelector("main")?.id === "main_results";
|
||||
|
||||
const results = await res.json();
|
||||
// focus search input on large screens
|
||||
if (!(isMobile || isResultsPage)) {
|
||||
qInput.focus();
|
||||
}
|
||||
|
||||
const autocomplete = document.querySelector<HTMLElement>(".autocomplete");
|
||||
assertElement(autocomplete);
|
||||
createClearButton(qInput);
|
||||
|
||||
const autocompleteList = document.querySelector<HTMLUListElement>(".autocomplete ul");
|
||||
assertElement(autocompleteList);
|
||||
// Additionally to searching when selecting a new category, we also
|
||||
// automatically start a new search request when the user changes a search
|
||||
// filter (safesearch, time range or language) (this requires JavaScript
|
||||
// though)
|
||||
if (
|
||||
settings.search_on_category_select &&
|
||||
// If .search_filters is undefined (invisible) we are on the homepage and
|
||||
// hence don't have to set any listeners
|
||||
document.querySelector(".search_filters")
|
||||
) {
|
||||
const safesearchElement = document.getElementById("safesearch");
|
||||
if (safesearchElement) {
|
||||
listen("change", safesearchElement, () => submitIfQuery(qInput));
|
||||
}
|
||||
|
||||
autocomplete.classList.add("open");
|
||||
autocompleteList.replaceChildren();
|
||||
const timeRangeElement = document.getElementById("time_range");
|
||||
if (timeRangeElement) {
|
||||
listen("change", timeRangeElement, () => submitIfQuery(qInput));
|
||||
}
|
||||
|
||||
// show an error message that no result was found
|
||||
if (!results?.[1]?.length) {
|
||||
const noItemFoundMessage = Object.assign(document.createElement("li"), {
|
||||
className: "no-item-found",
|
||||
textContent: searxng.settings.translations?.no_item_found ?? "No results found"
|
||||
});
|
||||
autocompleteList.append(noItemFoundMessage);
|
||||
const languageElement = document.getElementById("language");
|
||||
if (languageElement) {
|
||||
listen("change", languageElement, () => submitIfQuery(qInput));
|
||||
}
|
||||
}
|
||||
|
||||
const categoryButtons: HTMLButtonElement[] = [
|
||||
...document.querySelectorAll<HTMLButtonElement>("button.category_button")
|
||||
];
|
||||
for (const button of categoryButtons) {
|
||||
listen("click", button, (event: MouseEvent) => {
|
||||
if (event.shiftKey) {
|
||||
event.preventDefault();
|
||||
button.classList.toggle("selected");
|
||||
return;
|
||||
}
|
||||
|
||||
const fragment = new DocumentFragment();
|
||||
|
||||
for (const result of results[1]) {
|
||||
const li = Object.assign(document.createElement("li"), { textContent: result });
|
||||
|
||||
searxng.listen("mousedown", li, () => {
|
||||
qInput.value = result;
|
||||
|
||||
const form = document.querySelector<HTMLFormElement>("#search");
|
||||
form?.submit();
|
||||
|
||||
autocomplete.classList.remove("open");
|
||||
});
|
||||
|
||||
fragment.append(li);
|
||||
// deselect all other categories
|
||||
for (const categoryButton of categoryButtons) {
|
||||
categoryButton.classList.toggle("selected", categoryButton === button);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
autocompleteList.append(fragment);
|
||||
} catch (error) {
|
||||
console.error("Error fetching autocomplete results:", error);
|
||||
const form: HTMLFormElement | null = document.querySelector<HTMLFormElement>("#search");
|
||||
assertElement(form);
|
||||
|
||||
// override form submit action to update the actually selected categories
|
||||
listen("submit", form, (event: Event) => {
|
||||
event.preventDefault();
|
||||
|
||||
const categoryValuesInput = document.querySelector<HTMLInputElement>("#selected-categories");
|
||||
if (categoryValuesInput) {
|
||||
const categoryValues = categoryButtons
|
||||
.filter((button) => button.classList.contains("selected"))
|
||||
.map((button) => button.name.replace("category_", ""));
|
||||
|
||||
categoryValuesInput.value = categoryValues.join(",");
|
||||
}
|
||||
};
|
||||
|
||||
searxng.ready(
|
||||
() => {
|
||||
const qInput = document.getElementById("q") as HTMLInputElement | null;
|
||||
assertElement(qInput);
|
||||
|
||||
const isMobile = window.matchMedia("(max-width: 50em)").matches;
|
||||
const isResultsPage = document.querySelector("main")?.id === "main_results";
|
||||
|
||||
// focus search input on large screens
|
||||
if (!isMobile && !isResultsPage) {
|
||||
qInput.focus();
|
||||
}
|
||||
|
||||
createClearButton(qInput);
|
||||
|
||||
// autocompleter
|
||||
if (searxng.settings.autocomplete) {
|
||||
let timeoutId: number;
|
||||
|
||||
searxng.listen("input", qInput, () => {
|
||||
clearTimeout(timeoutId);
|
||||
|
||||
const query = qInput.value;
|
||||
const minLength = searxng.settings.autocomplete_min ?? 2;
|
||||
|
||||
if (query.length < minLength) return;
|
||||
|
||||
timeoutId = window.setTimeout(async () => {
|
||||
if (query === qInput.value) {
|
||||
await fetchResults(qInput, query);
|
||||
}
|
||||
}, 300);
|
||||
});
|
||||
|
||||
const autocomplete = document.querySelector<HTMLElement>(".autocomplete");
|
||||
const autocompleteList = document.querySelector<HTMLUListElement>(".autocomplete ul");
|
||||
if (autocompleteList) {
|
||||
searxng.listen("keyup", qInput, (event: KeyboardEvent) => {
|
||||
const listItems = [...autocompleteList.children] as HTMLElement[];
|
||||
|
||||
const currentIndex = listItems.findIndex((item) => item.classList.contains("active"));
|
||||
let newCurrentIndex = -1;
|
||||
|
||||
switch (event.key) {
|
||||
case "ArrowUp": {
|
||||
const currentItem = listItems[currentIndex];
|
||||
if (currentItem && currentIndex >= 0) {
|
||||
currentItem.classList.remove("active");
|
||||
}
|
||||
// we need to add listItems.length to the index calculation here because the JavaScript modulos
|
||||
// operator doesn't work with negative numbers
|
||||
newCurrentIndex = (currentIndex - 1 + listItems.length) % listItems.length;
|
||||
break;
|
||||
}
|
||||
case "ArrowDown": {
|
||||
const currentItem = listItems[currentIndex];
|
||||
if (currentItem && currentIndex >= 0) {
|
||||
currentItem.classList.remove("active");
|
||||
}
|
||||
newCurrentIndex = (currentIndex + 1) % listItems.length;
|
||||
break;
|
||||
}
|
||||
case "Tab":
|
||||
case "Enter":
|
||||
if (autocomplete) {
|
||||
autocomplete.classList.remove("open");
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (newCurrentIndex !== -1) {
|
||||
const selectedItem = listItems[newCurrentIndex];
|
||||
if (selectedItem) {
|
||||
selectedItem.classList.add("active");
|
||||
|
||||
if (!selectedItem.classList.contains("no-item-found")) {
|
||||
const qInput = document.getElementById("q") as HTMLInputElement | null;
|
||||
if (qInput) {
|
||||
qInput.value = selectedItem.textContent ?? "";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Additionally to searching when selecting a new category, we also
|
||||
// automatically start a new search request when the user changes a search
|
||||
// filter (safesearch, time range or language) (this requires JavaScript
|
||||
// though)
|
||||
if (
|
||||
searxng.settings.search_on_category_select &&
|
||||
// If .search_filters is undefined (invisible) we are on the homepage and
|
||||
// hence don't have to set any listeners
|
||||
document.querySelector(".search_filters")
|
||||
) {
|
||||
const safesearchElement = document.getElementById("safesearch");
|
||||
if (safesearchElement) {
|
||||
searxng.listen("change", safesearchElement, () => submitIfQuery(qInput));
|
||||
}
|
||||
|
||||
const timeRangeElement = document.getElementById("time_range");
|
||||
if (timeRangeElement) {
|
||||
searxng.listen("change", timeRangeElement, () => submitIfQuery(qInput));
|
||||
}
|
||||
|
||||
const languageElement = document.getElementById("language");
|
||||
if (languageElement) {
|
||||
searxng.listen("change", languageElement, () => submitIfQuery(qInput));
|
||||
}
|
||||
}
|
||||
|
||||
const categoryButtons = [...document.querySelectorAll<HTMLButtonElement>("button.category_button")];
|
||||
for (const button of categoryButtons) {
|
||||
searxng.listen("click", button, (event: MouseEvent) => {
|
||||
if (event.shiftKey) {
|
||||
event.preventDefault();
|
||||
button.classList.toggle("selected");
|
||||
return;
|
||||
}
|
||||
|
||||
// deselect all other categories
|
||||
for (const categoryButton of categoryButtons) {
|
||||
categoryButton.classList.toggle("selected", categoryButton === button);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const form = document.querySelector<HTMLFormElement>("#search");
|
||||
assertElement(form);
|
||||
|
||||
// override form submit action to update the actually selected categories
|
||||
searxng.listen("submit", form, (event: Event) => {
|
||||
event.preventDefault();
|
||||
|
||||
const categoryValuesInput = document.querySelector<HTMLInputElement>("#selected-categories");
|
||||
if (categoryValuesInput) {
|
||||
const categoryValues = categoryButtons
|
||||
.filter((button) => button.classList.contains("selected"))
|
||||
.map((button) => button.name.replace("category_", ""));
|
||||
|
||||
categoryValuesInput.value = categoryValues.join(",");
|
||||
}
|
||||
|
||||
form.submit();
|
||||
});
|
||||
},
|
||||
{ on: [searxng.endpoint === "index" || searxng.endpoint === "results"] }
|
||||
);
|
||||
form.submit();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user