[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`](1ad832b1dc/client/simple/src/js/toolkit.ts (L134))
array has the plugin id.

* [mod] client/simple: rebuild static
This commit is contained in:
Ivan Gabaldon
2025-12-02 10:18:00 +00:00
committed by GitHub
parent ab8224c939
commit fb089ae297
92 changed files with 962 additions and 881 deletions

View File

@@ -37,6 +37,9 @@ plugins:
searx.plugins.calculator.SXNGPlugin:
active: true
searx.plugins.infinite_scroll.SXNGPlugin:
active: false
searx.plugins.hash_plugin.SXNGPlugin:
active: true

View File

@@ -1,99 +0,0 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
# pylint: disable=missing-module-docstring,disable=missing-class-docstring,invalid-name
import warnings
from parameterized.parameterized import parameterized
import searx.plugins
import searx.preferences
from searx.extended_types import sxng_request
from searx.result_types import Answer
from tests import SearxTestCase
from .test_utils import random_string
from .test_plugins import do_post_search
# Reporting the DeprecationWarning once should be sufficient when running tests
warnings.filterwarnings("once", category=DeprecationWarning)
class PluginCalculator(SearxTestCase):
def setUp(self):
super().setUp()
engines = {}
self.storage = searx.plugins.PluginStorage()
self.storage.load_settings({"searx.plugins.calculator.SXNGPlugin": {"active": True}})
self.storage.init(self.app)
self.pref = searx.preferences.Preferences(["simple"], ["general"], engines, self.storage)
self.pref.parse_dict({"locale": "en"})
def test_plugin_store_init(self):
self.assertEqual(1, len(self.storage))
def test_pageno_1_2(self):
with self.app.test_request_context():
sxng_request.preferences = self.pref
query = "1+1"
answer = Answer(answer=f"{query} = {eval(query)}") # pylint: disable=eval-used
search = do_post_search(query, self.storage, pageno=1)
self.assertIn(answer, search.result_container.answers)
search = do_post_search(query, self.storage, pageno=2)
self.assertEqual(list(search.result_container.answers), [])
def test_long_query_ignored(self):
with self.app.test_request_context():
sxng_request.preferences = self.pref
query = f"1+1 {random_string(101)}"
search = do_post_search(query, self.storage)
self.assertEqual(list(search.result_container.answers), [])
@parameterized.expand(
[
("1+1", "2", "en"),
("1-1", "0", "en"),
("1*1", "1", "en"),
("1/1", "1", "en"),
("1**1", "1", "en"),
("1^1", "1", "en"),
("1,000.0+1,000.0", "2,000", "en"),
("1.0+1.0", "2", "en"),
("1.0-1.0", "0", "en"),
("1.0*1.0", "1", "en"),
("1.0/1.0", "1", "en"),
("1.0**1.0", "1", "en"),
("1.0^1.0", "1", "en"),
("1.000,0+1.000,0", "2.000", "de"),
("1,0+1,0", "2", "de"),
("1,0-1,0", "0", "de"),
("1,0*1,0", "1", "de"),
("1,0/1,0", "1", "de"),
("1,0**1,0", "1", "de"),
("1,0^1,0", "1", "de"),
]
)
def test_localized_query(self, query: str, res: str, lang: str):
with self.app.test_request_context():
self.pref.parse_dict({"locale": lang})
sxng_request.preferences = self.pref
answer = Answer(answer=f"{query} = {res}")
search = do_post_search(query, self.storage)
self.assertIn(answer, search.result_container.answers)
@parameterized.expand(
[
"1/0",
]
)
def test_invalid_operations(self, query):
with self.app.test_request_context():
sxng_request.preferences = self.pref
search = do_post_search(query, self.storage)
self.assertEqual(list(search.result_container.answers), [])