|
23 | 23 | } |
24 | 24 |
|
25 | 25 | function createSearchInterface() { |
26 | | - // Find the intro section to insert search box after it |
27 | | - const introSection = document.querySelector('.wg-blank'); |
28 | | - if (!introSection) return; |
29 | | - |
30 | | - // Create search container |
31 | | - const searchContainer = document.createElement('div'); |
32 | | - searchContainer.className = 'cluster-search-container'; |
33 | | - searchContainer.innerHTML = ` |
34 | | - <div class="container" style="margin-bottom: 30px; margin-top: 20px;"> |
35 | | - <div class="row justify-content-center"> |
36 | | - <div class="col-lg-8"> |
37 | | - <div class="input-group"> |
38 | | - <input type="text" |
39 | | - id="clusterSearchInput" |
40 | | - class="form-control" |
41 | | - placeholder="Search within all clusters and sub-categories..." |
42 | | - aria-label="Search clusters"> |
43 | | - <div class="input-group-append"> |
44 | | - <button class="btn btn-primary" type="button" id="clusterSearchBtn"> |
45 | | - <i class="fas fa-search"></i> Search |
46 | | - </button> |
47 | | - <button class="btn btn-outline-secondary" type="button" id="clusterClearBtn" style="display: none;"> |
48 | | - <i class="fas fa-times"></i> Clear |
49 | | - </button> |
50 | | - </div> |
| 26 | + // Create sidebar container |
| 27 | + const sidebar = document.createElement('div'); |
| 28 | + sidebar.id = 'clusterSearchSidebar'; |
| 29 | + sidebar.className = 'cluster-search-sidebar'; |
| 30 | + sidebar.innerHTML = ` |
| 31 | + <button class="cluster-search-toggle" id="clusterSearchToggle" aria-label="Toggle search panel"> |
| 32 | + <i class="fas fa-search"></i> Search Clusters |
| 33 | + </button> |
| 34 | + <div class="cluster-search-panel" id="clusterSearchPanel"> |
| 35 | + <div class="cluster-search-header"> |
| 36 | + <h4>Search Clusters</h4> |
| 37 | + <button class="cluster-search-close" id="clusterSearchClose" aria-label="Close search"> |
| 38 | + <i class="fas fa-times"></i> |
| 39 | + </button> |
| 40 | + </div> |
| 41 | + <div class="cluster-search-body"> |
| 42 | + <div class="input-group"> |
| 43 | + <input type="text" |
| 44 | + id="clusterSearchInput" |
| 45 | + class="form-control" |
| 46 | + placeholder="Search clusters..." |
| 47 | + aria-label="Search clusters"> |
| 48 | + <div class="input-group-append"> |
| 49 | + <button class="btn btn-primary btn-sm" type="button" id="clusterSearchBtn"> |
| 50 | + <i class="fas fa-search"></i> |
| 51 | + </button> |
51 | 52 | </div> |
52 | | - <div id="clusterSearchResults" style="margin-top: 10px; font-size: 0.9rem;"></div> |
53 | 53 | </div> |
| 54 | + <button class="btn btn-outline-secondary btn-sm btn-block mt-2" type="button" id="clusterClearBtn" style="display: none;"> |
| 55 | + <i class="fas fa-times"></i> Clear Results |
| 56 | + </button> |
| 57 | + <div id="clusterSearchResults" style="margin-top: 15px; font-size: 0.85rem;"></div> |
54 | 58 | </div> |
55 | 59 | </div> |
56 | 60 | `; |
57 | 61 |
|
58 | | - // Insert after intro section |
59 | | - introSection.parentNode.insertBefore(searchContainer, introSection.nextSibling); |
| 62 | + // Insert at beginning of body |
| 63 | + document.body.insertBefore(sidebar, document.body.firstChild); |
60 | 64 | } |
61 | 65 |
|
62 | 66 | function setupSearchHandlers() { |
63 | 67 | const searchInput = document.getElementById('clusterSearchInput'); |
64 | 68 | const searchBtn = document.getElementById('clusterSearchBtn'); |
65 | 69 | const clearBtn = document.getElementById('clusterClearBtn'); |
66 | 70 | const resultsDiv = document.getElementById('clusterSearchResults'); |
| 71 | + const toggleBtn = document.getElementById('clusterSearchToggle'); |
| 72 | + const closeBtn = document.getElementById('clusterSearchClose'); |
| 73 | + const panel = document.getElementById('clusterSearchPanel'); |
| 74 | + |
| 75 | + if (!searchInput || !searchBtn || !clearBtn || !toggleBtn || !closeBtn || !panel) return; |
| 76 | + |
| 77 | + // Toggle sidebar |
| 78 | + toggleBtn.addEventListener('click', function() { |
| 79 | + panel.classList.toggle('open'); |
| 80 | + if (panel.classList.contains('open')) { |
| 81 | + searchInput.focus(); |
| 82 | + } |
| 83 | + }); |
67 | 84 |
|
68 | | - if (!searchInput || !searchBtn || !clearBtn) return; |
| 85 | + // Close sidebar |
| 86 | + closeBtn.addEventListener('click', function() { |
| 87 | + panel.classList.remove('open'); |
| 88 | + }); |
| 89 | + |
| 90 | + // Close on escape key |
| 91 | + document.addEventListener('keydown', function(e) { |
| 92 | + if (e.key === 'Escape' && panel.classList.contains('open')) { |
| 93 | + panel.classList.remove('open'); |
| 94 | + } |
| 95 | + }); |
69 | 96 |
|
70 | 97 | // Search on button click |
71 | 98 | searchBtn.addEventListener('click', performSearch); |
|
198 | 225 | return; |
199 | 226 | } |
200 | 227 |
|
201 | | - let html = `<div class="alert alert-success">Found ${results.length} tab(s) containing "${escapeHtml(query)}" (${results.reduce((sum, r) => sum + r.matches, 0)} total matches). Click on a result to view it:</div>`; |
202 | | - html += '<div class="list-group" style="margin-top: 10px;">'; |
| 228 | + let html = `<div class="search-summary">Found ${results.length} tab(s) with ${results.reduce((sum, r) => sum + r.matches, 0)} matches</div>`; |
| 229 | + html += '<div class="search-results-list">'; |
203 | 230 |
|
204 | 231 | results.forEach(function(result) { |
205 | 232 | html += ` |
206 | | - <a href="#" class="list-group-item list-group-item-action cluster-search-result" |
207 | | - data-tab-id="${result.tabId}"> |
208 | | - <div class="d-flex w-100 justify-content-between"> |
209 | | - <h6 class="mb-1">${escapeHtml(result.cluster)} → ${escapeHtml(result.tab)}</h6> |
210 | | - <small>${result.matches} match${result.matches > 1 ? 'es' : ''}</small> |
| 233 | + <div class="search-result-item" data-tab-id="${result.tabId}"> |
| 234 | + <div class="search-result-header"> |
| 235 | + <strong>${escapeHtml(result.tab)}</strong> |
| 236 | + <span class="badge badge-primary">${result.matches}</span> |
211 | 237 | </div> |
212 | | - <p class="mb-1"><small>${result.snippet}</small></p> |
213 | | - </a> |
| 238 | + <div class="search-result-cluster">${escapeHtml(result.cluster)}</div> |
| 239 | + <div class="search-result-snippet">${result.snippet}</div> |
| 240 | + </div> |
214 | 241 | `; |
215 | 242 | }); |
216 | 243 |
|
217 | 244 | html += '</div>'; |
218 | 245 | resultsDiv.innerHTML = html; |
219 | 246 |
|
220 | 247 | // Add click handlers to results |
221 | | - const resultLinks = resultsDiv.querySelectorAll('.cluster-search-result'); |
222 | | - resultLinks.forEach(function(link) { |
223 | | - link.addEventListener('click', function(e) { |
224 | | - e.preventDefault(); |
| 248 | + const resultItems = resultsDiv.querySelectorAll('.search-result-item'); |
| 249 | + resultItems.forEach(function(item) { |
| 250 | + item.addEventListener('click', function() { |
225 | 251 | const tabId = this.getAttribute('data-tab-id'); |
226 | 252 | const result = results.find(r => r.tabId === tabId); |
227 | 253 | if (result) { |
228 | 254 | activateTab(result); |
229 | 255 | highlightMatches(result.tabPane, query); |
230 | 256 | // Scroll to the section |
231 | 257 | result.section.scrollIntoView({ behavior: 'smooth', block: 'start' }); |
| 258 | + // Mark as active |
| 259 | + resultItems.forEach(r => r.classList.remove('active')); |
| 260 | + this.classList.add('active'); |
232 | 261 | } |
233 | 262 | }); |
234 | 263 | }); |
235 | 264 |
|
236 | 265 | // Auto-expand first result |
237 | 266 | if (results.length > 0) { |
| 267 | + resultItems[0].classList.add('active'); |
238 | 268 | activateTab(results[0]); |
239 | 269 | highlightMatches(results[0].tabPane, query); |
240 | 270 | } |
|
322 | 352 | }); |
323 | 353 | } |
324 | 354 |
|
325 | | - // Add CSS for highlighting |
| 355 | + // Add CSS for sidebar and highlighting |
326 | 356 | const style = document.createElement('style'); |
327 | 357 | style.textContent = ` |
| 358 | + /* Highlight styling */ |
328 | 359 | .cluster-highlight { |
329 | 360 | background-color: #ffeb3b; |
330 | 361 | padding: 2px 0; |
331 | 362 | font-weight: bold; |
332 | 363 | } |
333 | 364 | |
334 | | - .cluster-search-container { |
335 | | - position: sticky; |
336 | | - top: 70px; |
| 365 | + /* Sidebar toggle button */ |
| 366 | + .cluster-search-toggle { |
| 367 | + position: fixed; |
| 368 | + left: 0; |
| 369 | + top: 200px; |
| 370 | + background: #007bff; |
| 371 | + color: white; |
| 372 | + border: none; |
| 373 | + border-radius: 0 5px 5px 0; |
| 374 | + padding: 12px 15px; |
| 375 | + cursor: pointer; |
| 376 | + z-index: 1000; |
| 377 | + box-shadow: 2px 2px 5px rgba(0,0,0,0.2); |
| 378 | + font-size: 14px; |
| 379 | + transition: background 0.3s; |
| 380 | + } |
| 381 | + |
| 382 | + .cluster-search-toggle:hover { |
| 383 | + background: #0056b3; |
| 384 | + } |
| 385 | + |
| 386 | + .cluster-search-toggle i { |
| 387 | + margin-right: 5px; |
| 388 | + } |
| 389 | + |
| 390 | + /* Sidebar panel */ |
| 391 | + .cluster-search-panel { |
| 392 | + position: fixed; |
| 393 | + left: -350px; |
| 394 | + top: 0; |
| 395 | + width: 350px; |
| 396 | + height: 100vh; |
337 | 397 | background: white; |
338 | | - z-index: 100; |
339 | | - padding: 20px 0 10px 0; |
340 | | - box-shadow: 0 2px 4px rgba(0,0,0,0.1); |
| 398 | + box-shadow: 2px 0 10px rgba(0,0,0,0.1); |
| 399 | + z-index: 1001; |
| 400 | + transition: left 0.3s ease; |
| 401 | + display: flex; |
| 402 | + flex-direction: column; |
| 403 | + overflow: hidden; |
| 404 | + } |
| 405 | + |
| 406 | + .cluster-search-panel.open { |
| 407 | + left: 0; |
| 408 | + } |
| 409 | + |
| 410 | + /* Sidebar header */ |
| 411 | + .cluster-search-header { |
| 412 | + display: flex; |
| 413 | + justify-content: space-between; |
| 414 | + align-items: center; |
| 415 | + padding: 15px 20px; |
| 416 | + border-bottom: 1px solid #dee2e6; |
| 417 | + background: #f8f9fa; |
| 418 | + } |
| 419 | + |
| 420 | + .cluster-search-header h4 { |
| 421 | + margin: 0; |
| 422 | + font-size: 18px; |
| 423 | + color: #333; |
341 | 424 | } |
342 | 425 | |
343 | | - .cluster-search-result:hover { |
| 426 | + .cluster-search-close { |
| 427 | + background: none; |
| 428 | + border: none; |
| 429 | + font-size: 20px; |
| 430 | + color: #666; |
344 | 431 | cursor: pointer; |
| 432 | + padding: 5px; |
| 433 | + line-height: 1; |
| 434 | + } |
| 435 | + |
| 436 | + .cluster-search-close:hover { |
| 437 | + color: #333; |
| 438 | + } |
| 439 | + |
| 440 | + /* Sidebar body */ |
| 441 | + .cluster-search-body { |
| 442 | + padding: 20px; |
| 443 | + flex: 1; |
| 444 | + overflow-y: auto; |
| 445 | + } |
| 446 | + |
| 447 | + /* Search results summary */ |
| 448 | + .search-summary { |
| 449 | + background: #e7f3ff; |
| 450 | + padding: 10px; |
| 451 | + border-radius: 5px; |
| 452 | + margin-bottom: 15px; |
| 453 | + font-size: 0.9rem; |
| 454 | + color: #004085; |
345 | 455 | } |
346 | 456 | |
347 | | - .cluster-search-result mark { |
| 457 | + /* Search results list */ |
| 458 | + .search-results-list { |
| 459 | + display: flex; |
| 460 | + flex-direction: column; |
| 461 | + gap: 10px; |
| 462 | + } |
| 463 | + |
| 464 | + /* Individual search result */ |
| 465 | + .search-result-item { |
| 466 | + background: #f8f9fa; |
| 467 | + border: 1px solid #dee2e6; |
| 468 | + border-radius: 5px; |
| 469 | + padding: 12px; |
| 470 | + cursor: pointer; |
| 471 | + transition: all 0.2s; |
| 472 | + } |
| 473 | + |
| 474 | + .search-result-item:hover { |
| 475 | + background: #e9ecef; |
| 476 | + border-color: #007bff; |
| 477 | + box-shadow: 0 2px 5px rgba(0,0,0,0.1); |
| 478 | + } |
| 479 | + |
| 480 | + .search-result-item.active { |
| 481 | + background: #e7f3ff; |
| 482 | + border-color: #007bff; |
| 483 | + box-shadow: inset 0 0 0 1px #007bff; |
| 484 | + } |
| 485 | + |
| 486 | + .search-result-header { |
| 487 | + display: flex; |
| 488 | + justify-content: space-between; |
| 489 | + align-items: center; |
| 490 | + margin-bottom: 5px; |
| 491 | + } |
| 492 | + |
| 493 | + .search-result-header strong { |
| 494 | + font-size: 0.95rem; |
| 495 | + color: #333; |
| 496 | + flex: 1; |
| 497 | + margin-right: 10px; |
| 498 | + } |
| 499 | + |
| 500 | + .search-result-header .badge { |
| 501 | + font-size: 0.75rem; |
| 502 | + } |
| 503 | + |
| 504 | + .search-result-cluster { |
| 505 | + font-size: 0.8rem; |
| 506 | + color: #666; |
| 507 | + margin-bottom: 8px; |
| 508 | + } |
| 509 | + |
| 510 | + .search-result-snippet { |
| 511 | + font-size: 0.8rem; |
| 512 | + color: #555; |
| 513 | + line-height: 1.4; |
| 514 | + } |
| 515 | + |
| 516 | + .search-result-snippet mark { |
348 | 517 | background-color: #ffeb3b; |
349 | 518 | padding: 1px 2px; |
| 519 | + font-weight: 600; |
| 520 | + } |
| 521 | + |
| 522 | + /* Mobile responsive */ |
| 523 | + @media (max-width: 768px) { |
| 524 | + .cluster-search-panel { |
| 525 | + width: 100%; |
| 526 | + left: -100%; |
| 527 | + } |
| 528 | + |
| 529 | + .cluster-search-panel.open { |
| 530 | + left: 0; |
| 531 | + } |
| 532 | + |
| 533 | + .cluster-search-toggle { |
| 534 | + top: 150px; |
| 535 | + font-size: 12px; |
| 536 | + padding: 10px 12px; |
| 537 | + } |
| 538 | + } |
| 539 | + |
| 540 | + /* Alert styling in sidebar */ |
| 541 | + .cluster-search-body .alert { |
| 542 | + font-size: 0.85rem; |
| 543 | + padding: 8px 12px; |
350 | 544 | } |
351 | 545 | `; |
352 | 546 | document.head.appendChild(style); |
|
0 commit comments