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) <noreply@anthropic.com>

---------

Co-authored-by: ponstream24 <87808547+ponstream24@users.noreply.github.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
dgtlmoon
2026-09-12 17:09:10 +02:00
committed by GitHub
co-authored by Claude Opus 5 ponstream24
parent 8d938b5966
commit cd66f9ed54
3 changed files with 16 additions and 41 deletions
+3 -39
View File
@@ -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.
});
})();
+4 -2
View File
@@ -317,11 +317,13 @@
<h2 class="modal-title" id="search-modal-title">{{ _('Search') }}</h2>
</div>
<div class="modal-body">
<form id="search-form" method="GET">
{# Plain GET submit to the watchlist - url_for() carries the reverse-proxy sub-path
(SCRIPT_NAME), so no client-side URL building is needed. #}
<form id="search-form" method="GET" action="{{ url_for('watchlist.index') }}">
<div class="pure-control-group">
<label for="search-modal-input">{% if active_tag_uuid %}{{ _("URL or Title in '%(title)s'", title=active_tag.title) }}{% else %}{{ _('URL or Title') }}{% endif %}</label>
<input id="search-modal-input" class="m-d" name="q" placeholder="{{ _('Enter search term...') }}" required type="text" value="" autofocus>
<input name="tags" type="hidden" value="{% if active_tag_uuid %}{{active_tag_uuid}}{% endif %}">
{% if active_tag_uuid %}<input name="tags" type="hidden" value="{{ active_tag_uuid }}">{% endif %}
</div>
</form>
</div>
+9
View File
@@ -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'<form id="search-form" method="GET" action="/">' in res.data
res = client.get("/", base_url="http://localhost/sub-path")
assert b'<form id="search-form" method="GET" action="/sub-path/">' in res.data