From a2dd5500831e8af588491dd9480699a9bf9dff0b Mon Sep 17 00:00:00 2001 From: Aayush Kumar Date: Wed, 22 Oct 2025 02:12:48 +0530 Subject: [PATCH 1/3] add basic search to resource tree Signed-off-by: Aayush Kumar --- .../panels/resource_search_results.html | 30 +++++ .../templates/scanpipe/resource_tree.html | 113 ++++++++++++++++++ scanpipe/urls.py | 5 + scanpipe/views.py | 22 ++++ 4 files changed, 170 insertions(+) create mode 100644 scanpipe/templates/scanpipe/panels/resource_search_results.html diff --git a/scanpipe/templates/scanpipe/panels/resource_search_results.html b/scanpipe/templates/scanpipe/panels/resource_search_results.html new file mode 100644 index 0000000000..08f7ca65a5 --- /dev/null +++ b/scanpipe/templates/scanpipe/panels/resource_search_results.html @@ -0,0 +1,30 @@ +{% if search_results %} +
+ {% for resource in search_results %} + + {% endfor %} +
+{% elif query %} +
+
+ +
+

No files found matching "{{ query }}"

+
+{% endif %} \ No newline at end of file diff --git a/scanpipe/templates/scanpipe/resource_tree.html b/scanpipe/templates/scanpipe/resource_tree.html index 6a598d4949..8497f0b9f7 100644 --- a/scanpipe/templates/scanpipe/resource_tree.html +++ b/scanpipe/templates/scanpipe/resource_tree.html @@ -83,6 +83,25 @@ background-color: var(--bulma-background); border-radius: var(--bulma-radius); } + .search-container { + position: sticky; + top: 0; + z-index: 100; + } + .search-dropdown { + position: absolute; + top: 100%; + left: 0; + right: 0; + z-index: 1000; + border: 1px solid var(--bulma-border); + border-radius: var(--bulma-radius); + background: var(--bulma-scheme-main); + box-shadow: var(--bulma-shadow); + max-height: 400px; + overflow-y: auto; + margin-top: 4px; + } {% endblock %} @@ -98,6 +117,36 @@
+
+
+
+ + + + +
+
+ +
+
+ +
+
{% include "scanpipe/panels/codebase_tree_panel.html" with children=children path=path %}
@@ -219,6 +268,70 @@ document.body.style.userSelect = ''; } }); + + const searchInput = document.getElementById('file-search-input'); + const searchResults = document.getElementById('search-results'); + const clearSearchBtn = document.getElementById('clear-search'); + + function toggleSearchResults(show = null) { + const shouldShow = show !== null ? show : searchInput.value.trim(); + searchResults.classList.toggle('is-hidden', !shouldShow); + } + + function clearSearch() { + searchInput.value = ''; + toggleSearchResults(false); + searchInput.focus(); + } + + function handleSearchResultClick(searchResultItem) { + const path = searchResultItem.dataset.path; + + clearSearch(); + + fetch(`{% url 'codebase_resource_table' project.slug %}?path=${encodeURIComponent(path)}`) + .then(response => response.text()) + .then(html => { + document.getElementById('right-pane').innerHTML = html; + htmx.process(document.getElementById('right-pane')); + if (typeof enableCopyToClipboard === 'function') { + enableCopyToClipboard('.copy-to-clipboard'); + } + const newUrl = `{% url 'codebase_resource_tree' project.slug %}?path=${encodeURIComponent(path)}`; + window.history.pushState(null, '', newUrl); + expandToPath(path); + }); + } + + searchInput.addEventListener('focus', () => toggleSearchResults()); + searchInput.addEventListener('input', () => toggleSearchResults()); + + clearSearchBtn.addEventListener('click', clearSearch); + + searchInput.addEventListener('keydown', function(e) { + if (e.key === 'Escape') { + clearSearch(); + } + }); + + document.addEventListener('click', function(e) { + const searchResultItem = e.target.closest('.dropdown-item'); + if (searchResultItem) { + e.preventDefault(); + handleSearchResultClick(searchResultItem); + return; + } + + if (!searchInput.contains(e.target) && !searchResults.contains(e.target)) { + toggleSearchResults(false); + } + }); + + document.body.addEventListener('htmx:afterSettle', function(evt) { + if (evt.target === searchResults) { + toggleSearchResults(); + } + }); }); {% endblock %} \ No newline at end of file diff --git a/scanpipe/urls.py b/scanpipe/urls.py index 67a457f94d..e5605f3063 100644 --- a/scanpipe/urls.py +++ b/scanpipe/urls.py @@ -136,6 +136,11 @@ views.CodebaseResourceTreeView.as_view(), name="codebase_resource_tree", ), + path( + "project//resource_search/", + views.CodebaseResourceSearchView.as_view(), + name="codebase_resource_search", + ), path( "project//resource_table/", views.CodebaseResourceTableView.as_view(), diff --git a/scanpipe/views.py b/scanpipe/views.py index 470b115c63..203e82abfb 100644 --- a/scanpipe/views.py +++ b/scanpipe/views.py @@ -2790,6 +2790,28 @@ def get(self, request, *args, **kwargs): return render(request, self.template_name, context) +class CodebaseResourceSearchView( + ConditionalLoginRequired, + ProjectRelatedViewMixin, + generic.ListView, +): + model = CodebaseResource + template_name = "scanpipe/panels/resource_search_results.html" + context_object_name = "search_results" + paginate_by = 30 + + def get_queryset(self): + qs = super().get_queryset() + search_query = self.request.GET.get("search", "").strip() + qs = qs.filter(path__icontains=search_query) + return qs.only("path", "type", "name").order_by("path") + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context["query"] = self.request.GET.get("search", "") + return context + + class CodebaseResourceTableView( ConditionalLoginRequired, ProjectRelatedViewMixin, From 2e2d3bd8b71b2ee385eeff053e6dbb26afbe9ac0 Mon Sep 17 00:00:00 2001 From: Aayush Kumar Date: Sat, 25 Oct 2025 01:38:12 +0530 Subject: [PATCH 2/3] move search component css to main.css Signed-off-by: Aayush Kumar --- scancodeio/static/main.css | 19 +++++++++++++++ .../templates/scanpipe/resource_tree.html | 24 ------------------- 2 files changed, 19 insertions(+), 24 deletions(-) diff --git a/scancodeio/static/main.css b/scancodeio/static/main.css index bb1622b852..b96ddd3eb2 100644 --- a/scancodeio/static/main.css +++ b/scancodeio/static/main.css @@ -557,3 +557,22 @@ body.full-screen #resource-viewer .message-header { background-color: var(--bulma-background); border-radius: var(--bulma-radius); } +#resource-tree-container .search-container { + position: sticky; + top: 0; + z-index: 100; +} +#resource-tree-container .search-dropdown { + position: absolute; + top: 100%; + left: 0; + right: 0; + z-index: 1000; + border: 1px solid var(--bulma-border); + border-radius: var(--bulma-radius); + background: var(--bulma-scheme-main); + box-shadow: var(--bulma-shadow); + max-height: 400px; + overflow-y: auto; + margin-top: 4px; +} diff --git a/scanpipe/templates/scanpipe/resource_tree.html b/scanpipe/templates/scanpipe/resource_tree.html index 2019507f85..7caac721c5 100644 --- a/scanpipe/templates/scanpipe/resource_tree.html +++ b/scanpipe/templates/scanpipe/resource_tree.html @@ -2,30 +2,6 @@ {% load static humanize %} {% block title %}ScanCode.io: {{ project.name }} - Resources tree{% endblock %} -{% block extrahead %} - -{% endblock %} - {% block content %}
{% include 'scanpipe/includes/navbar_header.html' %} From fdeeedd6f383c76f2dc9cab60d17ff4090663cb4 Mon Sep 17 00:00:00 2001 From: Aayush Kumar Date: Sat, 25 Oct 2025 01:52:09 +0530 Subject: [PATCH 3/3] fix search to retain search query after selection Signed-off-by: Aayush Kumar --- scanpipe/templates/scanpipe/resource_tree.html | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/scanpipe/templates/scanpipe/resource_tree.html b/scanpipe/templates/scanpipe/resource_tree.html index 7caac721c5..603cd2fdbc 100644 --- a/scanpipe/templates/scanpipe/resource_tree.html +++ b/scanpipe/templates/scanpipe/resource_tree.html @@ -183,7 +183,8 @@ function handleSearchResultClick(searchResultItem) { const path = searchResultItem.dataset.path; - clearSearch(); + toggleSearchResults(false); + searchInput.blur(); fetch(`{% url 'codebase_resource_table' project.slug %}?path=${encodeURIComponent(path)}`) .then(response => response.text()) @@ -206,7 +207,8 @@ searchInput.addEventListener('keydown', function(e) { if (e.key === 'Escape') { - clearSearch(); + toggleSearchResults(false); + searchInput.blur(); } });