summaryrefslogtreecommitdiff
path: root/public/javascripts/admin_search.js
diff options
context:
space:
mode:
authorerdgeist <erdgeist@erdgeist.org>2026-07-12 16:33:12 +0200
committererdgeist <erdgeist@erdgeist.org>2026-07-12 16:33:12 +0200
commit4ec70e5ecf792a56e868538738d6c7f63bb6cf24 (patch)
tree45364f298ce21a7e47de619b7db79b9b77e4ebc1 /public/javascripts/admin_search.js
parent5803c59192b7fb05840d0b452eb64d9f997f3d8f (diff)
Add a grouped tag+node search endpoint for the upcoming dashboard
AdminController#dashboard_search returns tags and nodes as separate, labeled groups (via ActsAsTaggableOn::Tag.named_like and Node.editor_search) rather than the flat per-node list every existing picker returns. initSearchPicker gains a renderResults callback option that, when given, replaces the default per-node rendering entirely -- lets the dashboard render its two labeled groups without a second, parallel picker implementation. resultsHeaderHtml (the "Press Enter to see all results" hint) is now threaded through as a third argument to renderResults, so a picker with custom rendering can still show it -- previously only the default per-node branch ever did. Tags link straight to the existing /admin/nodes/tags/:tag view rather than becoming an in-place filter chip.
Diffstat (limited to 'public/javascripts/admin_search.js')
-rw-r--r--public/javascripts/admin_search.js109
1 files changed, 77 insertions, 32 deletions
diff --git a/public/javascripts/admin_search.js b/public/javascripts/admin_search.js
index f553334..53bcb5e 100644
--- a/public/javascripts/admin_search.js
+++ b/public/javascripts/admin_search.js
@@ -42,9 +42,10 @@ function initSearchPicker(options) {
42 var inputSelector = options.inputSelector; 42 var inputSelector = options.inputSelector;
43 var resultsSelector = options.resultsSelector; 43 var resultsSelector = options.resultsSelector;
44 var url = options.url || ADMIN_MENU_SEARCH_URL; 44 var url = options.url || ADMIN_MENU_SEARCH_URL;
45 var onSelect = options.onSelect; // omit for a real-navigation search like admin_search 45 var onSelect = options.onSelect; // omit for a real-navigation search like admin_search
46 var isActive = options.isActive; // optional guard, checked before every search 46 var isActive = options.isActive; // optional guard, checked before every search
47 var resultsHeaderHtml = options.resultsHeaderHtml; // optional, prepended only when results exist 47 var resultsHeaderHtml = options.resultsHeaderHtml; // optional, prepended only when results exist
48 var renderResults = options.renderResults; // optional, replaces the default per-node rendering entirely
48 var requestId = 0; 49 var requestId = 0;
49 var timeout; 50 var timeout;
50 51
@@ -68,39 +69,44 @@ function initSearchPicker(options) {
68 url: url, 69 url: url,
69 data: "search_term=" + term, 70 data: "search_term=" + term,
70 dataType: "json", 71 dataType: "json",
71 success: function(nodes) { 72 success: function(data) {
72 if (thisRequest !== requestId) return; 73 if (thisRequest !== requestId) return;
73 results.empty(); 74 results.empty();
74 if (nodes.length && resultsHeaderHtml) { 75
75 results.append(resultsHeaderHtml); 76 var found;
76 } 77 if (renderResults) {
77 var found = false; 78 found = renderResults(data, results, resultsHeaderHtml);
78 for (var i = 0; i < nodes.length; i++) { 79 } else {
79 (function(node) { 80 if (data.length && resultsHeaderHtml) {
80 var link; 81 results.append(resultsHeaderHtml);
81 if (onSelect) { 82 }
82 link = $( 83 found = false;
83 "<p><a href='#'>" + node.title + 84 for (var i = 0; i < data.length; i++) {
84 "<span class='result_path'>" + node.unique_name + "</span>" + 85 (function(node) {
85 "</a></p>" 86 var link;
86 ); 87 if (onSelect) {
87 link.find("a").bind("click", function() { 88 link = $(
88 onSelect(node); 89 "<p><a href='#'>" + node.title +
89 results.slideUp(); 90 "<span class='result_path'>" + node.unique_name + "</span></a></p>"
90 results.empty(); 91 );
91 return false; 92 link.find("a").bind("click", function() {
92 }); 93 onSelect(node);
93 } else { 94 results.slideUp();
94 link = $( 95 results.empty();
95 "<p><a href='" + node.node_path + "'>" + node.title + 96 return false;
96 "<span class='result_path'>" + node.unique_name + "</span>" + 97 });
97 "</a></p>" 98 } else {
98 ); 99 link = $(
99 } 100 "<p><a href='" + node.node_path + "'>" + node.title +
100 results.append(link); 101 "<span class='result_path'>" + node.unique_name + "</span></a></p>"
101 found = true; 102 );
102 })(nodes[i]); 103 }
104 results.append(link);
105 found = true;
106 })(data[i]);
107 }
103 } 108 }
109
104 if (found) { 110 if (found) {
105 results.slideDown(); 111 results.slideDown();
106 } else { 112 } else {
@@ -115,6 +121,45 @@ function initSearchPicker(options) {
115 }); 121 });
116} 122}
117 123
124dashboard_search = {
125 initialize : function() {
126 initSearchPicker({
127 inputSelector: "#dashboard_search_term",
128 resultsSelector: "#dashboard_search_results",
129 url: DASHBOARD_SEARCH_URL,
130 resultsHeaderHtml: "<p class='search_more'>Press Enter to see all results ⏎</p>",
131 renderResults: function(data, results, resultsHeaderHtml) {
132 var found = false;
133
134 if ((data.tags.length || data.nodes.length) && resultsHeaderHtml) {
135 results.append(resultsHeaderHtml);
136 }
137
138 if (data.tags.length) {
139 results.append("<p class='search_group_label'>Tags</p>");
140 data.tags.forEach(function(tag) {
141 results.append("<p><a href='" + tag.tag_path + "'>" + tag.name + "</a></p>");
142 found = true;
143 });
144 }
145
146 if (data.nodes.length) {
147 results.append("<p class='search_group_label'>Pages</p>");
148 data.nodes.forEach(function(node) {
149 results.append(
150 "<p><a href='" + node.node_path + "'>" + node.title +
151 "<span class='result_path'>" + node.unique_name + "</span></a></p>"
152 );
153 found = true;
154 });
155 }
156
157 return found;
158 }
159 });
160 }
161};
162
118menu_items = { 163menu_items = {
119 initialize_search : function() { 164 initialize_search : function() {
120 initSearchPicker({ 165 initSearchPicker({