From cd66f9ed548de2b4faf94c7f94f228eaf30822e8 Mon Sep 17 00:00:00 2001 From: dgtlmoon Date: Sat, 12 Sep 2026 17:09:10 +0200 Subject: [PATCH] Search modal native submit (#4427) * UI - Search - Fix search modal navigating to the host root on sub-path deployments base_path was referenced by search-modal.js but never defined, so searching always jumped to the host root instead of the X-Forwarded-Prefix sub-path. * UI - Search modal - submit natively instead of rebuilding the URL in JS Alternative to defining a `base_path` JS global: give the search form a server-rendered `action`, so url_for() supplies the reverse-proxy sub-path the same way every other link on the page already does, and let the browser submit. Drops the submit handler and the Enter handler from search-modal.js - Enter in the input reaches the footer's submit button via implicit submission, which also runs the `required` validation the synthetic `new Event('submit')` skipped. The hidden tag field is only rendered when a tag is active, so a plain search no longer carries an empty value. Also drops the nginx-job grep for the rendered markup - test_search.py already covers the sub-path case, and asserting on an exact HTML attribute string from a shell grep breaks on any unrelated edit to that tag. Co-Authored-By: Claude Opus 5 (1M context) --------- Co-authored-by: ponstream24 <87808547+ponstream24@users.noreply.github.com> Co-authored-by: Claude Opus 5 (1M context) --- changedetectionio/static/js/search-modal.js | 42 ++------------------- changedetectionio/templates/base.html | 6 ++- changedetectionio/tests/test_search.py | 9 +++++ 3 files changed, 16 insertions(+), 41 deletions(-) diff --git a/changedetectionio/static/js/search-modal.js b/changedetectionio/static/js/search-modal.js index fe96186b5..ac4065332 100644 --- a/changedetectionio/static/js/search-modal.js +++ b/changedetectionio/static/js/search-modal.js @@ -7,7 +7,6 @@ // The Search button is rendered in the left rail and the mobile drawer. const openSearchButtons = document.querySelectorAll('.js-open-search-modal'); const closeSearchButton = document.getElementById('close-search-modal'); - const searchForm = document.getElementById('search-form'); const searchInput = document.getElementById('search-modal-input'); if (!searchModal || openSearchButtons.length === 0) { @@ -93,43 +92,8 @@ } }); - // Handle Enter key in search input - if (searchInput) { - searchInput.addEventListener('keydown', function(e) { - if (e.key === 'Enter') { - e.preventDefault(); - if (searchForm) { - // Trigger form submission programmatically - searchForm.dispatchEvent(new Event('submit')); - } - } - }); - } - - // Handle form submission - if (searchForm) { - searchForm.addEventListener('submit', function(e) { - e.preventDefault(); - - // Get form data - const formData = new FormData(searchForm); - const searchQuery = formData.get('q'); - const tags = formData.get('tags'); - - // Build URL - const params = new URLSearchParams(); - if (searchQuery) { - params.append('q', searchQuery); - } - if (tags) { - params.append('tags', tags); - } - - // Navigate to search results (always redirect to watchlist home) - // Use base_path if available (for sub-path deployments like /enlighten-richerx) - const basePath = typeof base_path !== 'undefined' ? base_path : ''; - window.location.href = basePath + '/?' + params.toString(); - }); - } + // Submission is left to the browser: the form carries a server-rendered action + // (correct under a reverse-proxy sub-path) and Enter in the input triggers implicit + // submission via the footer's submit button, which also runs `required` validation. }); })(); diff --git a/changedetectionio/templates/base.html b/changedetectionio/templates/base.html index 9c075bcca..93ae11d14 100644 --- a/changedetectionio/templates/base.html +++ b/changedetectionio/templates/base.html @@ -317,11 +317,13 @@ diff --git a/changedetectionio/tests/test_search.py b/changedetectionio/tests/test_search.py index e89b7a14a..df4560699 100644 --- a/changedetectionio/tests/test_search.py +++ b/changedetectionio/tests/test_search.py @@ -71,3 +71,12 @@ def test_search_in_tag_limit(client, live_server, measure_memory_usage, datastor assert urls[0].split(' ')[0].encode('utf-8') in res.data, urls[0].encode('utf-8') assert urls[1].split(' ')[0].encode('utf-8') not in res.data, urls[0].encode('utf-8') + +def test_search_modal_form_action(client, live_server, measure_memory_usage, datastore_path): + # The search modal submits as a plain GET form, so its action has to carry the + # reverse-proxy sub-path (SCRIPT_NAME), otherwise search jumps to the host root. + res = client.get(url_for("watchlist.index")) + assert b'
' in res.data + + res = client.get("/", base_url="http://localhost/sub-path") + assert b'' in res.data