-
Notifications
You must be signed in to change notification settings - Fork 0
feat: CEL expression docs + docs search (closes #1, #5) #6
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
d012699
8100636
376a26c
608cb82
9386a1a
43a03d6
3427e1a
4a17d73
cd68ac1
42cb334
e472689
83e9730
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| --- | ||
| // This component renders only a trigger button. | ||
| // The search modal is rendered once in Base.astro via SearchModal. | ||
| --- | ||
| <button | ||
| data-search-trigger | ||
| class="flex items-center gap-2 px-3 py-1.5 text-xs text-on-surface-variant border border-outline-variant bg-surface-container-low hover:border-outline transition-colors font-mono w-full lg:w-48" | ||
| type="button" | ||
| aria-label="Search documentation" | ||
| > | ||
| <svg class="w-3.5 h-3.5 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24"> | ||
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" /> | ||
| </svg> | ||
| <span class="flex-1 text-left">Search docs...</span> | ||
| <kbd class="hidden lg:inline text-[10px] text-on-surface-variant/60 border border-outline-variant/50 px-1 rounded-sm"><span class="shortcut-mac">⌘K</span><span class="shortcut-other hidden">Ctrl+K</span></kbd> | ||
| </button> | ||
| <script is:inline> | ||
| (function(){ | ||
| if (window.__shortcutDetected) return; | ||
| window.__shortcutDetected = true; | ||
| var isMac = /Mac|iPod|iPhone|iPad/.test(navigator.platform || navigator.userAgent || ''); | ||
| if (!isMac) { | ||
| document.querySelectorAll('.shortcut-mac').forEach(function(e){ e.classList.add('hidden'); }); | ||
| document.querySelectorAll('.shortcut-other').forEach(function(e){ e.classList.remove('hidden'); }); | ||
| } | ||
| })(); | ||
| </script> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,205 @@ | ||
| --- | ||
| // Single search modal rendered once in Base.astro. | ||
| // Multiple DocsSearch trigger buttons open this shared modal. | ||
| --- | ||
| <div id="search-modal" class="fixed inset-0 z-[100] hidden" role="dialog" aria-modal="true" aria-label="Search documentation"> | ||
| <div class="fixed inset-0 bg-black/60 backdrop-blur-sm" data-search-backdrop></div> | ||
| <div class="fixed top-[15%] left-1/2 -translate-x-1/2 w-[calc(100%-2rem)] max-w-xl bg-surface-container border border-outline-variant shadow-2xl max-h-[60vh] flex flex-col"> | ||
| <div class="flex items-center gap-3 px-4 py-3 border-b border-outline-variant"> | ||
| <svg class="w-4 h-4 text-on-surface-variant shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24"> | ||
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" /> | ||
| </svg> | ||
| <input | ||
| data-search-input | ||
| type="text" | ||
| placeholder="Search documentation..." | ||
| class="flex-1 bg-transparent text-on-surface text-sm outline-none placeholder:text-on-surface-variant/50 font-body" | ||
| autocomplete="off" | ||
| /> | ||
| <kbd class="text-[10px] text-on-surface-variant/60 border border-outline-variant/50 px-1.5 py-0.5 rounded-sm">Esc</kbd> | ||
| </div> | ||
| <div data-search-results class="overflow-y-auto p-2 text-sm"> | ||
| <p class="px-3 py-4 text-on-surface-variant/60 text-center text-xs">Type to search...</p> | ||
| </div> | ||
| </div> | ||
| </div> | ||
|
|
||
| <script is:inline> | ||
| (function() { | ||
| if (window.__searchInitialized) return; | ||
| window.__searchInitialized = true; | ||
|
|
||
| var pagefind = null; | ||
|
|
||
| function loadPagefind() { | ||
| if (pagefind) return Promise.resolve(pagefind); | ||
| return import('/pagefind/pagefind.js').then(function(pf) { | ||
| pagefind = pf; | ||
| return pf.init().then(function() { return pf; }); | ||
| }).catch(function() { pagefind = null; return null; }); | ||
| } | ||
|
|
||
| function getModal() { return document.getElementById('search-modal'); } | ||
| function getInput() { return document.querySelector('[data-search-input]'); } | ||
| function getResults() { return document.querySelector('[data-search-results]'); } | ||
|
|
||
| function setResultsText(msg) { | ||
| var r = getResults(); | ||
| if (!r) return; | ||
| while (r.firstChild) r.removeChild(r.firstChild); | ||
| var p = document.createElement('p'); | ||
| p.className = 'px-3 py-4 text-on-surface-variant/60 text-center text-xs'; | ||
| p.textContent = msg; | ||
| r.appendChild(p); | ||
| } | ||
|
|
||
| var previousFocus = null; | ||
| var searchRequestId = 0; | ||
|
|
||
| function openSearch() { | ||
| var modal = getModal(); | ||
| var input = getInput(); | ||
| if (!modal || !input) return; | ||
| previousFocus = document.activeElement; | ||
| searchRequestId++; // invalidate any pending requests | ||
| modal.classList.remove('hidden'); | ||
| input.value = ''; | ||
| input.focus(); | ||
| setResultsText('Type to search...'); | ||
| } | ||
|
|
||
| function closeSearch() { | ||
| var modal = getModal(); | ||
| if (modal) modal.classList.add('hidden'); | ||
| searchRequestId++; // invalidate any pending requests | ||
| if (previousFocus && previousFocus.focus) { | ||
| previousFocus.focus(); | ||
| previousFocus = null; | ||
| } | ||
| } | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
|
|
||
| // Trap focus inside the modal when open | ||
| document.addEventListener('keydown', function(e) { | ||
| if (e.key !== 'Tab') return; | ||
| var modal = getModal(); | ||
| if (!modal || modal.classList.contains('hidden')) return; | ||
|
|
||
| var focusable = modal.querySelectorAll('input, a, button, [tabindex]:not([tabindex="-1"])'); | ||
| if (focusable.length === 0) return; | ||
|
|
||
| var first = focusable[0]; | ||
| var last = focusable[focusable.length - 1]; | ||
|
|
||
| if (e.shiftKey) { | ||
| if (document.activeElement === first) { | ||
| e.preventDefault(); | ||
| last.focus(); | ||
| } | ||
| } else { | ||
| if (document.activeElement === last) { | ||
| e.preventDefault(); | ||
| first.focus(); | ||
| } | ||
| } | ||
| }); | ||
|
|
||
| // Recursively sanitize Pagefind excerpt: preserve only <mark> tags, strip everything else | ||
| function sanitizeExcerpt(html) { | ||
| var parser = new DOMParser(); | ||
| var doc = parser.parseFromString(html, 'text/html'); | ||
| var container = document.createElement('div'); | ||
| function walk(parent, target) { | ||
| for (var i = 0; i < parent.childNodes.length; i++) { | ||
| var node = parent.childNodes[i]; | ||
| if (node.nodeType === 3) { | ||
| target.appendChild(document.createTextNode(node.textContent)); | ||
| } else if (node.nodeType === 1 && node.tagName === 'MARK') { | ||
| var mark = document.createElement('mark'); | ||
| mark.setAttribute('data-pagefind-highlight', ''); | ||
| walk(node, mark); | ||
| target.appendChild(mark); | ||
| } else if (node.nodeType === 1) { | ||
| walk(node, target); | ||
| } | ||
| } | ||
| } | ||
| walk(doc.body, container); | ||
| return container; | ||
| } | ||
|
|
||
| document.addEventListener('keydown', function(e) { | ||
| if ((e.metaKey || e.ctrlKey) && e.key === 'k') { | ||
| e.preventDefault(); | ||
| var modal = getModal(); | ||
| if (!modal) return; | ||
| if (modal.classList.contains('hidden')) openSearch(); | ||
| else closeSearch(); | ||
| } | ||
| if (e.key === 'Escape') closeSearch(); | ||
| }); | ||
|
|
||
| document.addEventListener('click', function(e) { | ||
| if (e.target && e.target.hasAttribute('data-search-backdrop')) closeSearch(); | ||
| if (e.target && e.target.closest('[data-search-trigger]')) openSearch(); | ||
| }); | ||
|
|
||
| var debounceTimer; | ||
| document.addEventListener('input', function(e) { | ||
| if (!e.target || !e.target.hasAttribute('data-search-input')) return; | ||
| clearTimeout(debounceTimer); | ||
| debounceTimer = setTimeout(function() { | ||
| var input = e.target; | ||
| var results = getResults(); | ||
| if (!results) return; | ||
|
|
||
| var query = input.value.trim(); | ||
| if (!query) { | ||
| setResultsText('Type to search...'); | ||
| return; | ||
| } | ||
|
|
||
| var thisRequestId = ++searchRequestId; | ||
|
|
||
| loadPagefind().then(function(pf) { | ||
| if (!pf) { | ||
| setResultsText('Search unavailable (build required)'); | ||
| return; | ||
| } | ||
|
|
||
| pf.search(query).then(function(search) { | ||
| if (thisRequestId !== searchRequestId) return; // stale response | ||
| if (search.results.length === 0) { | ||
| setResultsText('No results for "' + query + '"'); | ||
| return; | ||
| } | ||
|
|
||
| Promise.all(search.results.slice(0, 8).map(function(r) { return r.data(); })) | ||
| .then(function(items) { | ||
| if (thisRequestId !== searchRequestId) return; // stale response | ||
| while (results.firstChild) results.removeChild(results.firstChild); | ||
| items.forEach(function(item) { | ||
| var link = document.createElement('a'); | ||
| link.href = item.url; | ||
| link.className = 'block px-3 py-2 hover:bg-surface-container-high transition-colors group'; | ||
|
|
||
| var title = document.createElement('div'); | ||
| title.className = 'font-medium text-on-surface text-sm group-hover:text-primary transition-colors'; | ||
| title.textContent = (item.meta && item.meta.title) || item.url; | ||
| link.appendChild(title); | ||
|
|
||
| if (item.excerpt) { | ||
| var excerptEl = sanitizeExcerpt(item.excerpt); | ||
| excerptEl.className = 'text-xs text-on-surface-variant mt-0.5 line-clamp-2'; | ||
| link.appendChild(excerptEl); | ||
| } | ||
|
|
||
| link.addEventListener('click', closeSearch); | ||
| results.appendChild(link); | ||
| }); | ||
| }); | ||
| }); | ||
| }); | ||
|
Comment on lines
+163
to
+201
|
||
| }, 200); | ||
| }); | ||
| })(); | ||
| </script> | ||
Uh oh!
There was an error while loading. Please reload this page.